VRFS-2795 merging feature/musician_profile_enhancements

This commit is contained in:
Jonathan Kolyer 2015-02-28 20:18:56 +00:00
commit d95279e694
66 changed files with 2304 additions and 451 deletions

View File

@ -26,6 +26,7 @@ ActiveAdmin.register JamRuby::JamTrack, :as => 'JamTracks' do
column :id
column :name
column :description
column :version
column :initial_play_silence
column :time_signature
column :status

View File

@ -7,7 +7,9 @@
| JamTrack should only be made available (to end users) if all its sub-component are in place:
= f.input :available, as: :boolean
= f.input :description, :input_html => { :rows=>5, :maxlength=>1000 }
= f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)'
= f.input :plan_code, :label=>'Recurly Plan Code', :required=>true, :hint => 'Must match plan code in Recurly'
= f.input :version, :label => 'Version', :hint => 'Increment this value whenever you invalidate (update) the definition of this JamTrack'
//= f.input :initial_play_silence, :label => 'Initial Play Silence (seconds)'
= f.input :time_signature, collection: JamRuby::JamTrack::TIME_SIGNATURES, include_blank: false
= f.input :status, collection: JamRuby::JamTrack::STATUS, include_blank: false
= f.input :recording_type, collection: JamRuby::JamTrack::RECORDING_TYPE, include_blank: false
@ -24,8 +26,9 @@
= f.input :reproduction_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
= f.input :licensor_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
= f.input :pro_royalty_amount, :required=>true, :input_html=>{type:'numeric'}
= f.input :plan_code, :label=>'Recurly Plan Code', :required=>true
= f.input :url, :as => :file, :label => 'Audio File'
= f.input :jmep_text, :as => :text, :label => "JMEP Text", :input_html => {:rows => 5 }
= f.input :jmep_json, :as => :text, :label => "JMEP Json", :input_html => {:rows => 5, :readonly=>true }, :hint => 'readonly'
= f.semantic_fields_for :jam_track_tracks do |track|
= render 'jam_track_track_fields', f: track

View File

@ -147,5 +147,8 @@ module JamAdmin
config.influxdb_hosts = ["localhost"]
config.influxdb_port = 8086
config.influxdb_ignored_environments = ENV["INFLUXDB_ENABLED"] == '1' ? ['test', 'cucumber'] : ['test', 'cucumber', 'development']
config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks"))
config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep"))
end
end

View File

@ -0,0 +1,25 @@
class JamRuby::JamTrack
# add a custom validation
before_save :jmep_json_generate
validate :jmep_text_validate
def jmep_text_validate
begin
JmepManager.execute(self.jmep_text)
rescue ArgumentError => err
errors.add(:jmep_text, err.to_s)
end
end
def jmep_json_generate
begin
self[:jmep_json] = JmepManager.execute(self.jmep_text)
rescue ArgumentError => err
#errors.add(:jmep_text, err.to_s)
end
end
end

2
build
View File

@ -66,7 +66,7 @@ DEB_SERVER=http://localhost:9010/apt-`uname -p`
GEM_SERVER=http://localhost:9000/gems
# if still going, then push all debs up
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* || "$GIT_BRANCH" == *release* ]]; then
if [[ "$GIT_BRANCH" == *develop* || "$GIT_BRANCH" == *master* || "$GIT_BRANCH" == *release* || "$GIT_BRANCH" == *feature* || "$GIT_BRANCH" == *hotfix* ]]; then
echo ""
echo "PUSHING DB ARTIFACTS"

View File

@ -258,4 +258,7 @@ user_presences_rename.sql
add_genre_type.sql
add_description_to_perf_samples.sql
widen_user_authorization_token.sql
jam_track_version.sql
recorded_jam_track_tracks.sql
jam_track_jmep_data.sql
musician_search.sql

View File

@ -0,0 +1,2 @@
ALTER TABLE jam_tracks ADD COLUMN jmep_text VARCHAR;
ALTER TABLE jam_tracks ADD COLUMN jmep_json JSON;

View File

@ -0,0 +1 @@
ALTER TABLE jam_tracks ADD COLUMN version VARCHAR NOT NULL DEFAULT 0;

View File

@ -1,15 +1,15 @@
ALTER TABLE recordings ADD COLUMN jam_track_id VARCHAR(64) REFERENCES jam_tracks(id);
ALTER TABLE recordings ADD COLUMN jam_track_id BIGINT REFERENCES jam_tracks(id);
ALTER TABLE recordings ADD COLUMN jam_track_initiator_id VARCHAR(64) REFERENCES users(id);
CREATE TABLE recorded_jam_track_tracks (
id BIGINT PRIMARY KEY,
user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
jam_track_track_id VARCHAR(64),
recording_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
jam_track_track_id VARCHAR(64) REFERENCES jam_track_tracks(id) NOT NULL,
recording_id VARCHAR(64) REFERENCES recordings(id) NOT NULL,
discard BOOLEAN,
timeline JSON,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
ALTER TABLE recorded_jam_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq');
ALTER TABLE recorded_jam_track_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq');

View File

@ -139,6 +139,7 @@ require "jam_ruby/models/recorded_backing_track_observer"
require "jam_ruby/models/recorded_track"
require "jam_ruby/models/recorded_track_observer"
require "jam_ruby/models/recorded_video"
require "jam_ruby/models/recorded_jam_track_track"
require "jam_ruby/models/quick_mix"
require "jam_ruby/models/quick_mix_observer"
require "jam_ruby/models/share_token"
@ -204,6 +205,7 @@ require "jam_ruby/models/user_sync"
require "jam_ruby/models/video_source"
require "jam_ruby/models/text_message"
require "jam_ruby/jam_tracks_manager"
require "jam_ruby/jmep_manager"
require "jam_ruby/models/performance_sample"
require "jam_ruby/models/online_presence"
require "jam_ruby/models/json_store"

View File

@ -20,7 +20,6 @@ module JamRuby
def save_jam_track_right_jkz(jam_track_right)
jam_track = jam_track_right.jam_track
#py_root = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "jamtracks"))
py_root = APP_CONFIG.jamtracks_dir
Dir.mktmpdir do |tmp_dir|
jam_file_opts=""
@ -42,10 +41,11 @@ module JamRuby
title=jam_track.name
output_jkz=File.join(tmp_dir, "#{title.parameterize}.jkz")
py_file = File.join(py_root, "jkcreate.py")
version = jam_track.version
@@log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output_jkz})"
# From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819:
cli = "python #{py_file} -D -k #{sku} -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem #{jam_file_opts} -o #{output_jkz} -t '#{title}'"
cli = "python #{py_file} -D -k #{sku} -p #{tmp_dir}/pkey.pem -s #{tmp_dir}/skey.pem #{jam_file_opts} -o #{output_jkz} -t '#{title}' -V '#{version}'"
Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr|
pid = wait_thr.pid
exit_status = wait_thr.value

View File

@ -0,0 +1,55 @@
require 'json'
require 'tempfile'
require 'open3'
require 'fileutils'
require 'open-uri'
module JamRuby
# Interact with external python tools to create jmep json
class JmepManager
@@log = Logging.logger[JmepManager]
class << self
def execute(jmep_text)
json = nil
if jmep_text.blank?
return nil
end
py_root = APP_CONFIG.jmep_dir
Dir.mktmpdir do |tmp_dir|
output_json = File.join(tmp_dir, "jmep.json")
input_text = File.join(tmp_dir, "jmep.txt")
# put JMEP text into input file
File.open(input_text, 'w') { |file| file.write(jmep_text) }
py_file = File.join(py_root, "jmepgen.py")
@@log.info "Executing python source in #{py_file}, outputting to #{output_json})"
# From http://stackoverflow.com/questions/690151/getting-output-of-system-calls-in-ruby/5970819#5970819:
cli = "python #{py_file} -i '#{input_text}' -o '#{output_json}'"
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)
raise ArgumentError, "#{out} #{err}" if exit_status != 0
json = File.read(output_json)
end
end
json
end
end
end
end

View File

@ -16,7 +16,7 @@ module JamRuby
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genre, :genre_id, :sales_region, :price,
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
:jam_track_tap_ins_attributes, :available, as: :admin
:jam_track_tap_ins_attributes, :available, :version, :jmep_json, :jmep_text, as: :admin
validates :name, presence: true, uniqueness: true, length: {maximum: 200}
validates :description, length: {maximum: 1000}
@ -30,6 +30,7 @@ module JamRuby
validates :sales_region, inclusion: {in: [nil] + SALES_REGION}
validates_format_of :price, with: /^\d+\.*\d{0,2}$/
validates :initial_play_silence, numericality: true, :allow_nil => true
validates :version, presence: true
validates :reproduction_royalty, inclusion: {in: [nil, true, false]}
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
@ -50,6 +51,8 @@ module JamRuby
has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession"
has_many :recordings, :class_name => "JamRuby::Recording"
accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true
accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true
@ -149,6 +152,7 @@ module JamRuby
def sanitize_active_admin
self.genre_id = nil if self.genre_id == ''
self.licensor_id = nil if self.licensor_id == ''
self.jmep_json = nil if self.jmep_json = ''
end
end
end

View File

@ -123,8 +123,6 @@ module JamRuby
state = nil
if signed
state = 'SIGNED'
elsif error_count > 0
state = 'ERROR'
elsif signing_started_at
if Time.now - signing_started_at > APP_CONFIG.signing_job_run_max_time
state = 'SIGNING_TIMEOUT'
@ -137,6 +135,8 @@ module JamRuby
else
state = 'QUEUED'
end
elsif error_count > 0
state = 'ERROR'
else
state = 'QUIET' # needs to be poked to go build
end

View File

@ -21,6 +21,8 @@ module JamRuby
belongs_to :instrument, class_name: "JamRuby::Instrument"
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :jam_track_track_id, :dependent => :destroy
# create storage directory that will house this jam_track, as well as
def store_dir
"#{jam_track.store_dir}/tracks"

View File

@ -141,12 +141,35 @@ module JamRuby
recording.recorded_tracks.each do |recorded_track|
manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
mix_params << { "level" => 100, "balance" => 0 }
mix_params << { "level" => 1.0, "balance" => 0 }
end
recording.recorded_backing_tracks.each do |recorded_backing_track|
manifest["files"] << { "filename" => recorded_backing_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
mix_params << { "level" => 100, "balance" => 0 }
mix_params << { "level" => 1.0, "balance" => 0 }
end
recording.recorded_jam_track_tracks.each do |recorded_jam_track_track|
manifest["files"] << { "filename" => recorded_jam_track_track.jam_track_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 }
# let's look for level info from the client
level = 1.0 # default value - means no effect
if recorded_jam_track_track.timeline
timeline_data = JSON.parse(recorded_jam_track_track.timeline)
# always take the 1st entry for now
first = timeline_data[0]
if first["mute"]
# mute equates to no noise
level = 0.0
else
# otherwise grab the left channel...
level = first["vol_l"]
end
end
mix_params << { "level" => level, "balance" => 0 }
end
manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }

View File

@ -0,0 +1,21 @@
module JamRuby
# BackingTrack analog to JamRuby::RecordedTrack
class RecordedJamTrackTrack < ActiveRecord::Base
belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_jam_track_tracks
belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_jam_track_tracks
belongs_to :jam_track_track, :class_name => "JamRuby::JamTrackTrack", :inverse_of => :recorded_jam_track_tracks
validates :user, presence: true
validates :jam_track_track, presence:true
def self.create_from_jam_track_track(jam_track_track, recording)
recorded_jam_track_track = self.new
recorded_jam_track_track.recording = recording
recorded_jam_track_track.jam_track_track = jam_track_track
recorded_jam_track_track.save
recorded_jam_track_track
end
end
end

View File

@ -12,6 +12,7 @@ module JamRuby
has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
has_many :recorded_videos, :class_name => "JamRuby::RecordedVideo", :foreign_key => :recording_id, :dependent => :destroy
has_many :recorded_backing_tracks, :class_name => "JamRuby::RecordedBackingTrack", :foreign_key => :recording_id, :dependent => :destroy
has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :recording_id, :dependent => :destroy
has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy
has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
@ -20,6 +21,8 @@ module JamRuby
belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings, :foreign_key => 'owner_id'
belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", :inverse_of => :recordings, foreign_key: :music_session_id
belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :inverse_of => :recordings, :foreign_key => 'jam_track_id'
belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :initiated_jam_track_recordings, :foreign_key => 'jam_track_initiator_id'
accepts_nested_attributes_for :recorded_tracks, :mixes, :claimed_recordings, allow_destroy: true
@ -223,6 +226,14 @@ module JamRuby
recording.recorded_backing_tracks << RecordedBackingTrack.create_from_backing_track(backing_track, recording)
end
end
if music_session.jam_track
music_session.jam_track.jam_track_tracks.each do |jam_track_track|
recording.recorded_jam_track_tracks << RecordedJamTrackTrack.create_from_jam_track_track(jam_track_track, recording)
end
recording.jam_track = music_session.jam_track
recording.jam_track_initiator = music_session.jam_track_initiator
end
end
end
@ -556,7 +567,7 @@ module JamRuby
:recording_id => recorded_item.recording_id,
:client_track_id => recorded_item.client_track_id,
:next => recorded_item.id
})
})
else
end
@ -678,6 +689,24 @@ module JamRuby
self.save(:validate => false)
end
def add_timeline(timeline)
tracks = timeline["tracks"]
raise JamArgumentError, "tracks must be specified" unless tracks
jam_tracks = tracks.select {|track| track["type"] == "jam_track"}
jam_tracks.each do |client_jam_track|
recorded_jam_track_track = RecordedJamTrackTrack.find_by_jam_track_track_id(client_jam_track["id"])
if recorded_jam_track_track
recorded_jam_track_track.timeline = client_jam_track["timeline"].to_json
recorded_jam_track_track.save!
else
@@log.error("unable to find JamTrackTrack with id #{recorded_jam_track_track.id}")
end
end
end
private
def self.validate_user_is_band_member(user, band)
unless band.users.exists? user

View File

@ -124,6 +124,10 @@ module JamRuby
has_many :recorded_videos, :foreign_key => "user_id", :class_name => "JamRuby::RecordedVideo", :inverse_of => :user
has_many :recorded_backing_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedBackingTrack", :inverse_of => :user
has_many :quick_mixes, :foreign_key => "user_id", :class_name => "JamRuby::QuickMix", :inverse_of => :user
has_many :recorded_jam_track_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedJamTrackTrack", :inverse_of => :user
# jam track recordings started
has_many :initiated_jam_track_recordings, :foreign_key => 'jam_track_initiator_id', :class_name => "JamRuby::Recording", :inverse_of => :jam_track_initiator
# invited users
has_many :invited_users, :foreign_key => "sender_id", :class_name => "JamRuby::InvitedUser"

View File

@ -111,6 +111,7 @@ module JamRuby
end
def place_order(current_user, jam_track)
jam_track_right = nil
account = get_account(current_user)
if (account.present?)
begin
@ -136,7 +137,7 @@ module JamRuby
else
raise RecurlyClientError, "Could not find account to place order."
end
account
jam_track_right
end
def find_or_create_account(current_user, billing_info=nil)

View File

@ -268,7 +268,6 @@ FactoryGirl.define do
association :recording, factory: :recording
end
factory :recorded_video, :class => JamRuby::RecordedVideo do
sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"}
fully_uploaded true
@ -277,6 +276,12 @@ FactoryGirl.define do
association :recording, factory: :recording
end
factory :recorded_jam_track_track, :class => JamRuby::RecordedJamTrackTrack do
association :user, factory: :user
association :recording, factory: :recording
association :jam_track_track, factory: :jam_track_track
end
factory :instrument, :class => JamRuby::Instrument do
description { |n| "Instrument #{n}" }
end

View File

@ -1073,6 +1073,33 @@ describe Recording do
RecordedVideo.find_by_id(video.id).should_not be_nil
end
end
describe "add_timeline" do
let!(:recorded_jam_track_track) {FactoryGirl.create(:recorded_jam_track_track)}
let(:recording) {recorded_jam_track_track.recording}
let(:timeline_data) {{"sample" => "data"}}
let(:good_timeline) { {
"tracks" => [
{
"id" => recorded_jam_track_track.jam_track_track.id,
"timeline" => timeline_data,
"type" => "jam_track"
}
]
}
}
it "applies timeline data correctly" do
recording.add_timeline good_timeline
recorded_jam_track_track.reload
JSON.parse(recorded_jam_track_track.timeline).should eq(timeline_data)
end
it "fails if no tracks data" do
expect { recording.add_timeline({}) }.to raise_error(JamRuby::JamArgumentError)
end
end
end

View File

@ -529,10 +529,12 @@
if(server.connecting) {
logger.error("server.connect should never be called if we are already connecting. cancelling.")
// XXX should return connectDeferred, but needs to be tested/vetted
return;
}
if(server.connected) {
logger.error("server.connect should never be called if we are already connected. cancelling.")
// XXX should return connectDeferred, but needs to be tested/vetted
return;
}
@ -678,7 +680,12 @@
logger.info("server.send(" + jsMessage + ")");
}
if (server !== undefined && server.socket !== undefined && server.socket.send !== undefined) {
server.socket.send(jsMessage);
try {
server.socket.send(jsMessage);
}
catch(err) {
logger.warn("error when sending on websocket: " + err)
}
} else {
logger.warn("Dropped message because server connection is closed.");
}

View File

@ -24,6 +24,7 @@
}
function beforeShow() {
$dialog.data('result', null)
emptyList();
resetPagination();
showing = true;
@ -77,18 +78,8 @@
// tell the server we are about to start a recording
rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
.done(function(response) {
context.jamClient.JamTrackStopPlay();
var result = context.jamClient.JamTrackPlay(jamTrack.id);
logger.debug("JamTrackPlay response: %o", result);
if(result) {
app.layout.closeDialog('open-jam-track-dialog');
}
else {
logger.error("unable to open jam track")
}
$dialog.data('result', {success:true, jamTrack: jamTrack})
app.layout.closeDialog('open-jam-track-dialog');
})
.fail(function(jqXHR) {
app.notifyServerError(jqXHR, "Unable to Open JamTrack For Playback");

View File

@ -2,10 +2,471 @@ $ = jQuery
context = window
context.JK ||= {};
context.JK.DownloadJamTrack = class SyncViewer
constructor: (@app) ->
# This is the sequence of how this widget works:
# checkState() is the heart of the state machine; it is called to get things going, and is called whenevr a state ends
# checkState() checks first against what the client thinks about the state of the JamTrack;
# if it on the disk then the state machine may enter one of:
# * synchronized
# * keying
#
# if it's still on the server, then the state machine may be:
# * packaging
# * downloading
#
# errored state can be entered from @jamTrack.jam_track_right_id
#
# other state; you augment the error to the user by suppling @errorMessage before transitioning
#
# no-client is the way the widget behaves when you are in a normal browser (i.e., nothing happens other than tell the user to use the client)
#
# Discussion of the different states:
# There are different states that a JamTrack can be in.
# The final success state is that the JamTrack is on disk and loadable. (show synchronized state)
# But there are others until you get there:
# The JamTrack does not exist on the server, so we will create it (packaging state)
# The JamTrack exists on the server, but not on disk, so we will download it (downloading state)
# The JamTrack is on the disk, but does not yet have keys, so we will fetch them (keying)
context.JK.DownloadJamTracks = {}
context.JK.DownloadJamTrack = class DownloadJamTrack
constructor: (@app, jamTrack, size = 'large') ->
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
@jamTrack = jamTrack
@size = size
@attemptedEnqueue = false
@errorReason = null
@errorMessage = null
@transitionTimer = null
@downloadTimer = null
@trackDetail = null
@stateHolder = null
@active = false
@startTime = null
@attempts = 0
@tracked = false
@ajaxEnqueueAborted = false
@ajaxGetJamTrackRightAborted = false
throw "no JamTrack specified" unless @jamTrack?
throw "invalid size" if @size != 'large' && @size != 'small'
throw "no JamTrack version" unless @jamTrack.version?
@path = []
@states = {
no_client: { name: 'no-client', show: @showNoClient, leaf: true },
synchronized: { name: 'synchronized', show: @showSynchronized, leaf: true},
packaging: { name: 'packaging', show: @showPackaging },
downloading: { name: 'downloading', show: @showDownloading },
keying: { name: 'keying', show: @showKeying, max_time: 10000 },
initial: { name: 'initial', show: @showInitial },
quiet: { name: 'quiet', show: @showQuiet },
errored: { name: 'errored', show: @showError, leaf: true}
}
context.JK.DownloadJamTracks[@jamTrack.id] = this
downloadJamTrackTemplate = $('#template-download-jamtrack')
throw "no download jamtrack template" if not downloadJamTrackTemplate.exists()
@root = $(downloadJamTrackTemplate.html())
@stateHolder = @root.find('.state')
@root.on('remove', this.destroy) # automatically destroy self when removed from DOM
# populate in template and visual transition functions
for state, data of @states
data.template = $("#template-download-jamtrack-state-#{data.name}")
# start off in quiet state, but don't do it through transition system. The transition system expects a change, not initial state
@state = @states.quiet
this.showState()
# after you've created the DownloadJamTrack widget, call synchronize which will begin ensuring that the jamtrack
# is downloaded and ready to open
init: () =>
@root = $($('#template-download-jamtrack').html())
@active = true
@root.addClass('active')
this.reset()
# check if we are in a browser or client
if !gon.isNativeClient
this.transition(@states.no_client)
else
this.transition(@states.initial)
# when done with the widget, call destroy; this ensures it's not still active, and tracks final metrics
destroy: () =>
$(this).off()
@active = false
@root.removeClass('active')
this.trackProgress()
# since we are not in a leave node, we need to report a state since this is effectively our end state
this.reset()
reset: () =>
@path = []
@attempts = 0
@tracked = false
@startTime = new Date()
# reset attemptedEnqueue to false, to allow one attempt to enqueue
@attemptedEnqueue = false
this.clearDownloadTimer()
this.clearTransitionTimer()
this.abortEnqueue()
this.abortGetJamTrackRight()
for state, data of @states
if data.timer?
clearInterval(data.timer)
data.timer = null
abortEnqueue: () =>
if @ajaxEnqueueAborted
@logger.debug("DownloadJamTrack: aborting ajax enqueue")
# we need to clear out @ajaxEnqueue *before* calling abort(), because the .fail callback fires inline
ajax = @ajaxEnqueueAborted
@ajaxEnqueueAborted = true
ajax.abort()
abortGetJamTrackRight: () =>
if @ajaxGetJamTrackRightAborted
@logger.debug("DownloadJamTrack: aborting ajax GetJamTrackRight")
# we need to clear out @ajaxEnqueue *before* calling abort(), because the .fail callback fires inline
ajax = @ajaxGetJamTrackRightAborted
@ajaxGetJamTrackRightAborted = true
ajax.abort()
showState: () =>
@state.stateStartTime = new Date();
@stateHolder.children().remove()
@stateHolder.append(context._.template(@state.template.html(), @jamTrack, { variable: 'data' }))
@stateHolder.find('.' + @size).removeClass('hidden')
@state.show()
# report a stat now that we've reached the end of this widget's journey
trackProgress: () =>
# do not double-report
if @tracked
return
if @path.length == 0
return
unless @state.leaf
# we've been asked to report at a non-leaf node, meaning the user must have cancelled
@path.push('user-cancelled')
flattened_path = @path.join('-')
data = {
value: 1,
path: flattened_path,
duration: (new Date().getTime() - @startTime.getTime()) / 1000,
attempts: @attempts,
user_id: context.JK.currentUserId,
user_name: context.JK.currentUserName}
if @state == @states.errored
data.result = 'error'
data.detail = @errorReason
else
data.result = 'success'
context.stats.write('web.jamtrack.downloader', data)
@tracked = true
showPackaging: () =>
@logger.debug("showing #{@state.name}")
this.expectTransition()
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, this.makeDownloadSuccessCallback(), this.makeDownloadFailureCallback())
showKeying: () =>
@logger.debug("showing #{@state.name}")
context.jamClient.JamTrackKeysRequest()
this.waitForState()
showQuiet: () =>
@logger.debug("showing #{@state.name}")
showInitial: () =>
@logger.debug("showing #{@state.name}")
@attempts = @attempts + 1
this.expectTransition()
context.JK.SubscriptionUtils.subscribe('jam_track_right', @jamTrack.jam_track_right_id).on(context.JK.EVENTS.SUBSCRIBE_NOTIFICATION, this.onJamTrackRightEvent)
this.checkState()
showError: () =>
@logger.debug("showing #{@state.name}")
context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrack.jam_track_right_id)
if @size == 'large'
@stateHolder.find('.msg').text(@errorMessage)
@stateHolder.find('.retry-button').click(this.retry)
else
@stateHolder.find('.msg').text(@jamTrack.name + ' (error)')
@stateHolder.find('.errormsg').text(@errorMessage)
@stateHolder.find('.retry-button').on('click', this.retry)
retryMsg = ''
if @attempts > 1
retryMsg = 'Continue retrying or contact support@jamkazam.com'
@stateHolder.find('.retry').text(retryMsg)
showSynchronized: () =>
@logger.debug("showing #{@state.name}")
context.JK.SubscriptionUtils.unsubscribe('jam_track_right', @jamTrack.jam_track_right_id)
showNoClient: () =>
@logger.debug("showing #{@state.name}")
downloadCheck: () =>
@logger.debug "downloadCheck"
retry: () =>
@path = []
@path.push('retry')
# just switch to the initial state again, causing the loop to start again
this.transition(@states.initial)
return false
clearStateTimer: () =>
if @state.timer?
clearInterval(@state.timer)
@state.timer = null
stateIntervalCheck: () =>
this.checkState()
# if the timer is null now, then it must have been whacked due to a state change
# if not, then let's see if we have timed out
if @state.timer?
if (new Date()).getTime() - @state.stateStartTime.getTime() > @state.max_time
@logger.debug("The current step (#{@state.name}) took too long")
if @state == @states.keying
# specific message
this.transitionError("#{@state.name}-timeout", "It took too long for the JamTrack to be keyed.")
else
# generic message
this.transitionError("#{@state.name}-timeout", "The current step (#{@state.name}) took too long")
# sets an interval timer for every second, waiting for the status to change
waitForState: () =>
unless @active
@logger.error("DownloadJamTrack: ignoring waitForState because we are not active")
@state.timer = setInterval(this.stateIntervalCheck, 1000)
# unused atm; the backend is good about always signalling. we still should though
expectDownload: () =>
unless @active
@logger.error("DownloadJamTrack: ignoring expectDownload because we are not active")
# every 10 seconds, wake up and check the server and see if we missed a state transition
this.clearDownloadTimer()
@downloadTimer = setTimeout(this.downloadCheck, 10000)
clearDownloadTimer: () =>
if @downloadTimer?
clearTimeout(@downloadTimer)
@downloadTimer = null
transitionError: (reasonCode, errorMessage) =>
@errorReason = reasonCode
@errorMessage = errorMessage
this.transition(@states.errored)
transitionCheck: () =>
this.checkState()
# this should be called every time something changes statefully, to restart a 12 second timer to hit the server for update.
# if everything is moving snappily, we won't have to query the server much, because we are also getting subscription events
# about any changes to the status of the jam track. But, we could miss a message or there could be a path in the server where
# we don't get an event, so that's why, after 12 seconds, we'll still go to the server and check.
# exception: this should not be runngi
expectTransition: () =>
unless @active
@logger.error("DownloadJamTrack: ignoring expectTransition because we are not active")
# every 12 seconds, wake up and check the server and see if we missed a state transition
this.clearTransitionTimer()
@transitionTimer = setTimeout(this.transitionCheck, 12000)
clearTransitionTimer: () =>
if @transitionTimer?
clearTimeout(@transitionTimer)
@transitionTimer = null
transition: (newState) =>
unless @active
@logger.error("DownloadJamTrack: ignoring state change because we are not active")
return
if newState == @state
@logger.debug("DownloadJamTrack: ignoring state change #{@state.name} #{newState}")
return
if @state?
@logger.debug("DownloadJamTrack: state change: #{@state.name} => #{newState.name}")
# make sure there is no timer running on the old state
this.clearTransitionTimer()
this.clearStateTimer()
this.abortEnqueue()
@logger.debug("aborting getJamTrack right on state change")
this.abortGetJamTrackRight()
else
@logger.debug("DownloadJamTrack: initial state: #{newState.name}")
@state = newState
# track which states were taken
@path.push(@state.name)
if @state.leaf
this.trackProgress()
this.showState()
$(this).triggerHandler(@EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, {state: @state})
checkState: () =>
# check for the success state against the local state of the client... if it's playable, then we should be OK
@trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id)
@logger.debug("DownloadJamTrack: JamTrackGetTrackDetail.key_state: " + @trackDetail.key_state)
# first check if the version is not the same; if so, invalidate.
if @trackDetail.version?
if @jamTrack.version != @trackDetail.version
@logger.info("DownloadJamTrack: JamTrack on disk is different version (stored: #{@trackDetail.version}, server: #{@jamTrack.version}. Invalidating")
context.jamClient.InvalidateJamTrack(@jamTrack.id)
@trackDetail = context.jamClient.JamTrackGetTrackDetail (@jamTrack.id)
if @trackDetail.version?
@logger.error("after invalidating package, the version is still wrong!")
throw "after invalidating package, the version is still wrong!"
switch @trackDetail.key_state
when 'pending'
this.transition(@states.keying)
when 'not authorized'
# TODO: if not authorized, do we need to re-initiate a keying attempt?
this.transition(@states.keying)
when 'ready'
this.transition(@states.synchronized)
when 'unknown'
@ajaxGetJamTrackRightAborted = false
@rest.getJamTrackRight({id: @jamTrack.id})
.done(this.processJamTrackRight)
.fail(this.processJamTrackRightFail)
processSigningState: (signingState) =>
@logger.debug("DownloadJamTrack: processSigningState: " + signingState)
switch signingState
when 'QUIET'
if @attemptedEnqueue
# this means we've already tried to poke the server. something is wrong
this.transitionError("enqueue-timeout", "The server has not begun building your JamTrack.")
else
this.expectTransition()
this.attemptToEnqueue()
when 'QUEUED'
# when it's queued, there is nothing to do except wait.
this.transition(@states.packaging)
when 'QUEUED_TIMEOUT'
this.transitionError("queued-timeout", "The server took too long to begin processing your JamTrack.")
when 'SIGNING'
this.transition(@states.packaging)
when 'SIGNING_TIMEOUT'
this.transitionError("signing-timeout", "The server took too long to create your JamTrack.")
when 'SIGNED'
this.transition(@states.downloading)
when 'ERROR'
if @attemptedEnqueue
# this means we've already tried to poke the server. something is wrong
this.transitionError("package-error", "The server failed to create your package.")
else
this.expectTransition()
this.attemptToEnqueue()
else
@logger.error("unknown state: " + signingState)
this.transitionError("unknown-state-#{signingState}", "The server sent an unknown state message: " + signingState)
attemptToEnqueue: () =>
@attemptedEnqueue = true
@ajaxEnqueueAborted = false
@rest.enqueueJamTrack({id: @jamTrack.id})
.done(this.processEnqueueJamTrack)
.fail(this.processEnqueueJamTrackFail)
processJamTrackRight: (myJamTrack) =>
unless @ajaxGetJamTrackRightAborted
this.processSigningState(myJamTrack.signing_state)
else
@logger.debug("DownloadJamTrack: ignoring processJamTrackRight response")
processJamTrackRightFail: () =>
unless @ajaxGetJamTrackRightAborted?
this.transitionError("status-check-error", "Unable to check with the server on the status of your JamTrack.")
else
@logger.debug("DownloadJamTrack: ignoring processJamTrackRightFail response")
processEnqueueJamTrack: (enqueueResponse) =>
unless @ajaxEnqueueAborted
this.expectTransition() # the act of enqueuing should send down events to the client. we wait...
else
@logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrack response")
processEnqueueJamTrackFail: () =>
unless @ajaxEnqueueAborted
this.transitionError("enqueue-error", "Unable to ask the server to build your JamTrack.")
else
@logger.debug("DownloadJamTrack: ignoring processEnqueueJamTrackFail response")
onJamTrackRightEvent: (e, data) =>
@logger.debug("DownloadJamTrack: subscription notification received: type:" + data.type)
this.expectTransition()
this.processSigningState(data.body.signing_state)
downloadProgressCallback: (bytesReceived, bytesTotal, downloadSpeedMegSec, timeRemaining) =>
bytesReceived = Number(bytesReceived)
bytesTotal = Number(bytesTotal)
# bytesTotal from Qt is not trust worthy; trust server's answer instead
#progressWidth = ((bytesReceived / updateSize) * 100).toString() + "%";
# $('#progress-bar').width(progressWidth)
downloadSuccessCallback: (updateLocation) =>
# is the package loadable yet?
@logger.debug("DownloadJamTrack: download complete - on to keying")
this.transition(@states.keying)
downloadFailureCallback: (errorMsg) =>
this.transitionError("download-error", errorMsg)
# makes a function name for the backend
makeDownloadProgressCallback: () =>
"JK.DownloadJamTracks['#{@jamTrack.id}'].downloadProgressCallback"
# makes a function name for the backend
makeDownloadSuccessCallback: () =>
"JK.DownloadJamTracks['#{@jamTrack.id}'].downloadSuccessCallback"
# makes a function name for the backend
makeDownloadFailureCallback: () =>
"JK.DownloadJamTracks['#{@jamTrack.id}'].downloadFailureCallback"

View File

@ -20,7 +20,8 @@
e.stopPropagation();
var $fader = $(this);
var sessionModel = window.JK.CurrentSessionModel || null;
var mediaControlsDisabled = $fader.data('media-controls-disabled');
if(mediaControlsDisabled) {
var mediaTrackOpener = $fader.data('media-track-opener');
@ -28,11 +29,16 @@
return false;
}
if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $control.closest('.session-track').data('track_data').type == 'jam_track') {
window.JK.prodBubble($fader, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $fader.closest('.screen')})
return false;
}
if($fader.data('showHelpAboutMediaMixers')) {
if(window.JK.CurrentSessionModel) {
if(!window.JK.CurrentSessionModel.hasShownAudioMediaMixerHelp()) {
if(sessionModel) {
if(!sessionModel.hasShownAudioMediaMixerHelp()) {
window.JK.prodBubble($fader, 'volume-media-mixers', {}, {positions:['top'], offsetParent: $fader.closest('.screen')})
window.JK.CurrentSessionModel.markShownAudioMediaMixerHelp()
sessionModel.markShownAudioMediaMixerHelp()
}
}
}
@ -138,10 +144,16 @@
var mediaControlsDisabled = $draggingFaderHandle.data('media-controls-disabled');
var mediaTrackOpener = $draggingFaderHandle.data('media-track-opener');
var sessionModel = window.JK.CurrentSessionModel || null;
if(mediaControlsDisabled) {
return false;
}
if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $draggingFaderHandle.closest('.session-track').data('track_data').type == 'jam_track') {
return false;
}
return true;
}

View File

@ -707,6 +707,16 @@
function TrackSetInstrument(track, instrumentId) {}
function JamTrackStopPlay() {}
function JamTrackPlay(){return true; }
function JamTrackIsPlayable() {
return true;
}
function JamTrackGetTrackDetail() {
return {key_state: 'unknown'}
}
function JamTrackKeysRequest() {}
function JamTrackDownload() {}
// Method which sets volume
function UpdateMixer(mixerId) {}
@ -1062,6 +1072,13 @@
this.TrackGetChatUsesMusic = TrackGetChatUsesMusic;
this.TrackSetChatUsesMusic = TrackSetChatUsesMusic;
this.JamTrackStopPlay = JamTrackStopPlay;
this.JamTrackPlay = JamTrackPlay;
this.JamTrackIsPlayable = JamTrackIsPlayable;
this.JamTrackGetTrackDetail = JamTrackGetTrackDetail;
this.JamTrackKeysRequest = JamTrackKeysRequest;
this.JamTrackDownload = JamTrackDownload;
// Scoring Knobs
this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval;
this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval;

View File

@ -45,7 +45,8 @@
SUBSCRIBE_NOTIFICATION: 'subscribe_notification',
CONNECTION_UP: 'connection_up',
CONNECTION_DOWN: 'connection_down',
SCREEN_CHANGED: 'screen_changed'
SCREEN_CHANGED: 'screen_changed',
JAMTRACK_DOWNLOADER_STATE_CHANGED: 'jamtrack_downloader_state'
};
context.JK.ALERT_NAMES = {

View File

@ -1448,6 +1448,28 @@
});
}
function getJamTrackRight(options) {
var jamTrackId = options['id'];
return $.ajax({
type: "GET",
url: '/api/jamtracks/rights/' + jamTrackId + '?' + $.param(options),
dataType: "json",
contentType: 'application/json'
})
}
function enqueueJamTrack(options) {
var jamTrackId = options['id'];
return $.ajax({
type: "POST",
url: '/api/jamtracks/enqueue?' + jamTrackId + '?' + $.param(options),
dataType: "json",
contentType: 'applications/json'
});
}
function getPurchasedJamTracks(options) {
return $.ajax({
type: "GET",
@ -1577,13 +1599,23 @@
});
}
function validateUrlSite(url, sitetype) {
function validateUrlSite(url, sitetype) {
return $.ajax({
type: "GET",
url: '/api/data_validation?sitetype='+sitetype+'&data=' + encodeURIComponent(url),
contentType: 'application/json'
});
}
function addRecordingTimeline(recordingId, data) {
return $.ajax({
type: "GET",
url: '/api/data_validation?sitetype='+sitetype+'&data=' + encodeURIComponent(url),
contentType: 'application/json'
type: "POST",
url: '/api/recordings/' + recordingId + '/timeline',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(data),
});
}
}
function initialize() {
return self;
@ -1709,6 +1741,8 @@
this.updateAudioLatency = updateAudioLatency;
this.getJamtracks = getJamtracks;
this.getPurchasedJamTracks = getPurchasedJamTracks;
this.getJamTrackRight = getJamTrackRight;
this.enqueueJamTrack = enqueueJamTrack;
this.getBackingTracks = getBackingTracks;
this.addJamtrackToShoppingCart = addJamtrackToShoppingCart;
this.getShoppingCarts = getShoppingCarts;
@ -1725,6 +1759,7 @@
this.createSourceChange = createSourceChange;
this.validateUrlSite = validateUrlSite;
this.markRecordedBackingTrackSilent = markRecordedBackingTrackSilent;
this.addRecordingTimeline = addRecordingTimeline;
return this;
};

View File

@ -4,9 +4,12 @@
context.JK = context.JK || {};
context.JK.OrderScreen = function(app) {
var EVENTS = context.JK.EVENTS;
var logger = context.JK.logger;
var $screen = null;
var $templateOrderContent = null;
var $templatePurchasedJamTrack = null;
var $navigation = null;
var $billingInfo = null;
var $shippingInfo = null;
@ -16,15 +19,23 @@
var $paymentInfoPanel = null;
var $orderPanel = null;
var $thanksPanel = null;
var $jamTrackInBrowser = null;
var $purchasedJamTrack = null;
var $purchasedJamTrackHeader = null;
var $purchasedJamTracks = null;
var $orderContent = null;
var userDetail = null;
var step = null;
var billing_info = null;
var shipping_info = null;
var shipping_as_billing = null;
var downloadJamTracks = [];
var purchasedJamTracks = null;
var purchasedJamTrackIterator = 0;
function beforeShow() {
beforeShowPaymentInfo();
beforeShowPaymentInfo();
resetJamTrackDownloadInfo();
}
function beforeShowPaymentInfo() {
@ -33,6 +44,12 @@
renderAccountInfo();
}
function resetJamTrackDownloadInfo() {
$purchasedJamTrack.addClass('hidden');
$purchasedJamTracks.children().remove()
$jamTrackInBrowser.hide('hidden');
}
function renderAccountInfo() {
rest.getUserDetail()
.done(populateAccountInfo)
@ -81,6 +98,21 @@
}
function afterShow(data) {
// XXX : style-test code
// moveToThanks({jam_tracks: [{id: 14, jam_track_right_id: 11, name: 'Back in Black'}, {id: 15, jam_track_right_id: 11, name: 'In Bloom'}, {id: 16, jam_track_right_id: 11, name: 'Love Bird Supreme'}]});
}
function beforeHide() {
if(downloadJamTracks) {
context._.each(downloadJamTracks, function(downloadJamTrack) {
downloadJamTrack.destroy();
downloadJamTrack.root.remove();
})
downloadJamTracks = [];
}
purchasedJamTracks = null;
purchasedJamTrackIterator = 0;
}
function next(e) {
@ -412,7 +444,7 @@
data.shipping_as_billing = shipping_as_billing
var orderContentHtml = $(
context._.template(
$('#template-order-content').html(),
$templateOrderContent.html(),
data,
{variable: 'data'}
)
@ -430,13 +462,86 @@
beforeShowOrder();
}
function moveToThanks() {
function moveToThanks(purchaseResponse) {
$("#order_error").addClass("hidden")
$paymentInfoPanel.addClass("hidden")
$orderPanel.addClass("hidden")
$thanksPanel.removeClass("hidden")
rest.clearShoppingCart()
beforeShowOrder()
handleJamTracksPurchased(purchaseResponse.jam_tracks)
}
function handleJamTracksPurchased(jamTracks) {
// were any JamTracks purchased?
var jamTracksPurchased = jamTracks && jamTracks.length > 0;
if(jamTracksPurchased) {
if(gon.isNativeClient) {
startDownloadJamTracks(jamTracks)
}
else {
$jamTrackInBrowser.removeClass('hidden');
}
}
}
function startDownloadJamTracks(jamTracks) {
// there can be multiple purchased JamTracks, so we cycle through them
purchasedJamTracks = jamTracks;
// populate list of jamtracks purchased, that we will iterate through graphically
context._.each(jamTracks, function(jamTrack) {
var downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'small');
var $purchasedJamTrack = $(context._.template(
$templatePurchasedJamTrack.html(),
jamTrack,
{variable: 'data'}
));
$purchasedJamTracks.append($purchasedJamTrack)
// show it on the page
$purchasedJamTrack.append(downloadJamTrack.root)
downloadJamTracks.push(downloadJamTrack)
})
iteratePurchasedJamTracks();
}
function iteratePurchasedJamTracks() {
if(purchasedJamTrackIterator < purchasedJamTracks.length ) {
var downloadJamTrack = downloadJamTracks[purchasedJamTrackIterator++];
// make sure the 'purchasing JamTrack' section can be seen
$purchasedJamTrack.removeClass('hidden');
// the widget indicates when it gets to any transition; we can hide it once it reaches completion
$(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) {
if(data.state == downloadJamTrack.states.synchronized) {
logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " synchronized;")
//downloadJamTrack.root.remove();
downloadJamTrack.destroy();
// go to the next JamTrack
iteratePurchasedJamTracks()
}
})
logger.debug("jamtrack " + downloadJamTrack.jamTrack.name + " downloader initializing")
// kick off the download JamTrack process
downloadJamTrack.init()
// XXX style-test code
// downloadJamTrack.transitionError("package-error", "The server failed to create your package.")
}
else {
logger.debug("done iterating over purchased JamTracks")
$purchasedJamTrackHeader.text('All purchased JamTracks have been downloaded successfully! You can now play them in a session.')
}
}
function moveToPaymentInfo(e) {
@ -506,21 +611,28 @@
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
'afterShow': afterShow
'afterShow': afterShow,
'beforeHide' : beforeHide
};
app.bindScreen('order', screenBindings);
$screen = $("#orderScreen");
$paymentInfoPanel = $screen.find(".checkout-payment-info");
$orderPanel = $screen.find(".order-panel");
$thanksPanel = $screen.find(".thanks-panel");
$navigation = $screen.find(".checkout-navigation-bar");
$billingInfo = $paymentInfoPanel.find(".billing-address");
$shippingInfo = $paymentInfoPanel.find(".shipping-address");
$paymentMethod = $paymentInfoPanel.find(".payment-method");
$shippingAddress = $paymentInfoPanel.find(".shipping-address-detail");
$shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing");
$orderContent = $orderPanel.find(".order-content");
$screen = $("#orderScreen");
$templateOrderContent = $("#template-order-content");
$templatePurchasedJamTrack = $('#template-purchased-jam-track');
$paymentInfoPanel = $screen.find(".checkout-payment-info");
$orderPanel = $screen.find(".order-panel");
$thanksPanel = $screen.find(".thanks-panel");
$jamTrackInBrowser = $screen.find(".thanks-detail.jam-tracks-in-browser");
$purchasedJamTrack = $thanksPanel.find(".thanks-detail.purchased-jam-track");
$purchasedJamTrackHeader = $purchasedJamTrack.find(".purchased-jam-track-header");
$purchasedJamTracks = $purchasedJamTrack.find(".purchased-list")
$navigation = $screen.find(".checkout-navigation-bar");
$billingInfo = $paymentInfoPanel.find(".billing-address");
$shippingInfo = $paymentInfoPanel.find(".shipping-address");
$paymentMethod = $paymentInfoPanel.find(".payment-method");
$shippingAddress = $paymentInfoPanel.find(".shipping-address-detail");
$shippingAsBilling = $paymentInfoPanel.find("#shipping-as-billing");
$orderContent = $orderPanel.find(".order-content");
if($screen.length == 0) throw "$screen must be specified";
if($navigation.length == 0) throw "$navigation must be specified";

View File

@ -47,6 +47,7 @@
var canUpdateBackend = false;
var playbackMode = PlaybackMode.EveryWhere;
var monitorPlaybackTimeout = null;
var jamTrackMode = false; // if true, we use different APIs to determine playback info
function startPlay() {
updateIsPlaying(true);
@ -67,7 +68,7 @@
playbackPositionMs = parseInt((offsetLeft / sliderBarWidth) * playbackDurationMs);
updateCurrentTimeText(playbackPositionMs);
if(canUpdateBackend) {
$self.triggerHandler('change-position', {positionMs: playbackPositionMs});
$self.triggerHandler('change-position', {positionMs: playbackPositionMs, jamTrackMode: jamTrackMode});
canUpdateBackend = false;
}
}
@ -100,13 +101,25 @@
}
$playButton.on('click', function(e) {
startPlay();
var sessionModel = context.JK.CurrentSessionModel || null;
if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
context.JK.prodBubble($fader, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $playButton})
return false;
}
startPlay();
return false;
});
$pauseButton.on('click', function(e) {
stopPlay();
var sessionModel = context.JK.CurrentSessionModel || null;
if(sessionModel && sessionModel.areControlsLockedForJamTrackRecording() && $parentElement.closest('.session-track').data('track_data').type == 'jam_track') {
context.JK.prodBubble($pauseButton, 'jamtrack-controls-disabled', {}, {positions:['top'], offsetParent: $pauseButton})
return false;
}
stopPlay();
return false;
});
$sliderBar.on('click', function(e) {
@ -144,16 +157,31 @@
});
function monitorRecordingPlayback() {
if(jamTrackMode) {
var positionMs = context.jamClient.SessionCurrrentJamTrackPlayPosMs();
var duration = context.jamClient.SessionGetJamTracksPlayDurationMs();
var durationMs = duration.media_len;
var start = duration.start; // needed to understand start offset, and prevent slider from moving in tapins
//console.log("JamTrack start: " + start)
}
else {
var positionMs = context.jamClient.SessionCurrrentPlayPosMs();
var durationMs = context.jamClient.SessionGetTracksPlayDurationMs();
}
var isPlaying = context.jamClient.isSessionTrackPlaying();
var positionMs = context.jamClient.SessionCurrrentPlayPosMs();
var durationMs = context.jamClient.SessionGetTracksPlayDurationMs();
if(positionMs < 0) {
// bug in backend?
positionMs = 0;
}
update(positionMs, durationMs, isPlaying);
monitorPlaybackTimeout = setTimeout(monitorRecordingPlayback, 500);
}
function update(currentTimeMs, durationTimeMs, isPlaying) {
function update(currentTimeMs, durationTimeMs, isPlaying, offsetStart) {
if(dragging) {
return;
@ -175,6 +203,10 @@
}
}
if(currentTimeMs < offsetStart) {
currentTimeMs = 0; // this is to squelch movement during tap-in period
}
updateDurationTime(durationTimeMs);
updateCurrentTime(currentTimeMs);
updateIsPlaying(isPlaying);
@ -247,7 +279,10 @@
}
}
function startMonitor() {
function startMonitor(_jamTrackMode) {
jamTrackMode = !!_jamTrackMode;
monitorRecordingPlayback();
}

View File

@ -39,9 +39,9 @@
var $reverbNationPresence = $screen.find('#reverbnation-presence');
var $bandCampPresence = $screen.find('#bandcamp-presence');
var $fandalismPresence = $screen.find('#fandalism-presence');
var $soundCloudPresence = $screen.find('#youtube-presence');
var $soundCloudPresence = $screen.find('#facebook-presence');
var $youTubePresence = $screen.find('#twitter-presence');
var $youTubePresence = $screen.find('#youtube-presence');
var $facebookPresence = $screen.find('#facebook-presence');
var $twitterPresence = $screen.find('#twitter-presence');
// current interests
var $noInterests = $screen.find('#no-interests');
@ -96,6 +96,9 @@
var $btnAddFriend = $screen.find('#btn-add-friend');
var $btnFollowUser = $screen.find('#btn-follow-user');
var $btnMessageUser = $screen.find('#btn-message-user');
var $btnAddRecordings = $screen.find('.add-recordings');
var $btnAddSites = $screen.find('.add-sites');
var $btnAddInterests = $screen.find('.add-interests');
// social
var $socialLeft = $screen.find('.profile-social-left');
@ -426,15 +429,121 @@
function bindAbout() {
$instruments.empty();
if (!isCurrentUser()) {
$btnAddRecordings.hide();
$btnAddSites.hide();
$btnAddInterests.hide();
}
renderNameLocationStats();
renderBio();
renderMusicalExperience();
renderPerformanceSamples();
renderOnlinePresence();
renderInterests();
}
function renderNameLocationStats() {
// name
$userName.html(user.name);
// avatar
$avatar.attr('src', context.JK.resolveAvatarUrl(user.photo_url));
// instruments
// location
$location.html(user.location);
$age.html(user.age ? user.age + " years old" : "");
// stats
var text = user.friend_count > 1 || user.friend_count === 0 ? " Friends" : " Friend";
$friendStats.html('<span class="friend-count">' + user.friend_count + '</span>' + text);
text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower";
$followerStats.html('<span class="follower-count">' + user.follower_count + '</span>' + text);
if (isMusician()) {
text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session";
$sessionStats.html(user.session_count + text);
text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording";
$recordingStats.html(user.recording_count + text);
}
else {
text = " Following";
$followingStats.html(user.following_count + text);
text = user.favorite_count > 1 || user.favorite_count === 0 ? " Favorites" : " Favorite";
$favoriteStats.html(user.favorite_count + text);
}
}
function renderBio() {
function initializeBioVisibility() {
$showBio.hide();
$noBio.hide();
$biographyEditor.hide();
$bioTextArea.val(user.biography);
if(user.biography) {
$showBio.show();
$biographyText.text(user.biography).show();
}
else {
if(isCurrentUser()) {
$noBio.show();
}
}
}
initializeBioVisibility();
// $addBiographyButton.unbind('click').click(function() {
// $biographyEditor.val(user.biography).show();
// return false;
// });
$submitBiographyButton.unbind('click').click(function() {
var bio = $bioTextArea.val();
$bioTextArea.closest('div.field').removeClass('error');
$('.error-text', $bioTextArea.closest('div.field')).remove();
userDefer = rest.updateUser({
biography: bio
})
.done(function(response) {
user = response;
initializeBioVisibility();
})
.fail(function(jqXHR) {
if(jqXHR.status == 422) {
var errors = JSON.parse(jqXHR.responseText)
var biography = context.JK.format_errors("biography", errors);
if(biography != null) {
$bioTextArea.closest('div.field').addClass('error').end().after(biography);
}
else {
app.notifyServerError(jqXHR, "Unable to update biography")
}
}
else {
app.notifyServerError(jqXHR, "Unable to update biography")
}
})
return false;
})
$cancelBiographyButton.unbind('click').click(function() {
initializeBioVisibility();
return false;
})
}
function renderMusicalExperience() {
$instruments.empty();
if (user.instruments) {
for (var i = 0; i < user.instruments.length; i++) {
var instrument = user.instruments[i];
@ -471,28 +580,20 @@
// studio gigs
var studioGigCount = user.studio_session_count;
$studioCount.html(studioGigCount > 0 ? 'Has played ' + profileUtils.gigMap[studioGigCount] + ' studio session gigs' : NOT_SPECIFIED_TEXT);
}
// location
$location.html(user.location);
$age.html(user.age ? user.age + " years old" : "");
// stats
var text = user.friend_count > 1 || user.friend_count === 0 ? " Friends" : " Friend";
$friendStats.html('<span class="friend-count">' + user.friend_count + '</span>' + text);
text = user.follower_count > 1 || user.follower_count === 0 ? " Followers" : " Follower";
$followerStats.html('<span class="follower-count">' + user.follower_count + '</span>' + text);
// text = user.following_count > 1 || user.following_count === 0 ? " Followings" : " Following";
// $('#profile-following-stats').html('<span class="following-count">' + user.following_count + '</span>' + text);
function renderPerformanceSamples() {
// performance samples
var performanceSamples = user.performance_samples;
if (!performanceSamples || performanceSamples.length === 0) {
$noSamples.show();
if (isCurrentUser()) {
$btnAddRecordings.show();
}
}
else {
$btnAddRecordings.hide();
$noSamples.hide();
// show samples section
@ -512,24 +613,88 @@
$youTubeSamples.append("<a href=''>" + sample.service_id + "</a>");
});
}
}
function renderOnlinePresence() {
// online presences
var onlinePresences = user.online_presences;
if ((!onlinePresences || onlinePresences.length === 0) && !user.website) {
$noOnlinePresence.show();
if (isCurrentUser()) {
$btnAddSites.show();
}
else {
$btnAddSites.hide();
}
}
else {
$noOnlinePresence.hide();
$btnAddSites.hide();
$noOnlinePresence.hide();
if (user.website) {
$userWebsite.append("");
}
$.each(onlinePresences, function(index, presence) {
});
if (user.website) {
$userWebsite.find('a').attr('href', user.website);
}
var soundCloudPresences = profileUtils.soundCloudPresences(onlinePresences);
if (soundCloudPresences && soundCloudPresences.length > 0) {
$soundCloudPresence.find('a').attr('href', 'http://www.soundcloud.com/' + soundCloudPresences[0].username);
}
else {
$soundCloudPresence.hide();
}
var reverbNationPresences = profileUtils.reverbNationPresences(onlinePresences);
if (reverbNationPresences && reverbNationPresences.length > 0) {
$reverbNationPresence.find('a').attr('href', 'http://www.reverbnation.com/' + reverbNationPresences[0].username);
}
else {
$reverbNationPresence.hide();
}
var bandCampPresences = profileUtils.bandCampPresences(onlinePresences);
if (bandCampPresences && bandCampPresences.length > 0) {
$bandCampPresence.find('a').attr('href', 'http://' + bandCampPresences[0].username + '.bandcamp.com/');
}
else {
$bandCampPresence.hide();
}
var fandalismPresences = profileUtils.fandalismPresences(onlinePresences);
if (fandalismPresences && fandalismPresences.length > 0) {
$fandalismPresence.find('a').attr('href', 'http://www.fandalism.com/' + fandalismPresences[0].username);
}
else {
$fandalismPresence.hide();
}
var youTubePresences = profileUtils.youTubePresences(onlinePresences);
if (youTubePresences && youTubePresences.length > 0) {
$youTubePresence.find('a').attr('href', 'http://www.youtube.com/' + youTubePresences[0].username);
}
else {
$youTubePresence.hide();
}
var facebookPresences = profileUtils.facebookPresences(onlinePresences);
if (facebookPresences && facebookPresences.length > 0) {
$facebookPresence.find('a').attr('href', 'http://www.facebook.com/' + facebookPresences[0].username);
}
else {
$facebookPresence.hide();
}
var twitterPresences = profileUtils.twitterPresences(onlinePresences);
if (twitterPresences && twitterPresences.length > 0) {
$twitterPresence.find('a').attr('href', 'http://www.twitter.com/' + twitterPresences[0].username);
}
else {
$twitterPresence.hide();
}
}
}
function renderInterests() {
// current interests
var noInterests = !user.paid_sessions && !user.free_sessions && !user.cowriting && !user.virtual_band && !user.traditional_band;
if (noInterests) {
@ -539,9 +704,16 @@
$cowritingSection.hide();
$traditionalBandSection.hide();
$virtualBandSection.hide();
if (isCurrentUser()) {
$btnAddInterests.show();
}
}
else {
$btnAddInterests.hide();
$noInterests.hide();
// paid sessions
if (user.paid_sessions) {
$paidGigSection.show();
@ -610,87 +782,6 @@
$virtualBandSection.hide();
}
}
if (isMusician()) {
text = user.session_count > 1 || user.session_count === 0 ? " Sessions" : " Session";
$sessionStats.html(user.session_count + text);
text = user.recording_count > 1 || user.recording_count === 0 ? " Recordings" : " Recording";
$recordingStats.html(user.recording_count + text);
} else {
text = " Following";
$followingStats.html(user.following_count + text);
text = user.favorite_count > 1 || user.favorite_count === 0 ? " Favorites" : " Favorite";
$favoriteStats.html(user.favorite_count + text);
}
renderBio();
}
/** The biography show/edit functionality */
function renderBio() {
function initializeBioVisibility() {
$showBio.hide();
$noBio.hide();
$biographyEditor.hide();
$bioTextArea.val(user.biography);
if(user.biography) {
$showBio.show();
$biographyText.text(user.biography).show();
}
else {
if(isCurrentUser()) {
$noBio.show();
}
}
}
initializeBioVisibility();
// $addBiographyButton.unbind('click').click(function() {
// $biographyEditor.val(user.biography).show();
// return false;
// });
$submitBiographyButton.unbind('click').click(function() {
var bio = $bioTextArea.val();
$bioTextArea.closest('div.field').removeClass('error');
$('.error-text', $bioTextArea.closest('div.field')).remove();
userDefer = rest.updateUser({
biography: bio
})
.done(function(response) {
user = response;
initializeBioVisibility();
})
.fail(function(jqXHR) {
if(jqXHR.status == 422) {
var errors = JSON.parse(jqXHR.responseText)
var biography = context.JK.format_errors("biography", errors);
if(biography != null) {
$bioTextArea.closest('div.field').addClass('error').end().after(biography);
}
else {
app.notifyServerError(jqXHR, "Unable to update biography")
}
}
else {
app.notifyServerError(jqXHR, "Unable to update biography")
}
})
return false;
})
$cancelBiographyButton.unbind('click').click(function() {
initializeBioVisibility();
return false;
})
}
/****************** SOCIAL TAB *****************/

View File

@ -232,7 +232,7 @@
profileUtils.twitterPresences = function(presences) {
var matches = $.grep(presences, function(p) {
return s.service_type === ONLINE_PRESENCE_TYPES.TWITTER.description;
return p.service_type === ONLINE_PRESENCE_TYPES.TWITTER.description;
});
return matches;

View File

@ -132,7 +132,6 @@
$self.triggerHandler('stoppedRecording', {'recordingId': recording.id, 'reason' : 'rest', 'details' : arguments});
}
});
});
return true;
}

View File

@ -82,6 +82,7 @@
var startingRecording = false; // double-click guard
var claimedRecording = null;
var backing_track_path = null;
var jamTrack = null;
var playbackControls = null;
var promptLeave = false;
var rateSessionDialog = null;
@ -93,6 +94,10 @@
var $screen = null;
var $mixModeDropdown = null;
var $templateMixerModeChange = null;
var $otherAudioContainer = null;
var $myTracksContainer = null;
var $liveTracksContainer = null;
var downloadJamTrack = null;
var $closePlaybackRecording = null;
var $openBackingTrack = null;
var mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup];
@ -106,7 +111,7 @@
window.location = '/client#/home';
}
promptLeave = true;
$('#session-mytracks-container').empty();
$myTracksContainer.empty();
displayDoneRecording(); // assumption is that you can't join a recording session, so this should be safe
var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session");
@ -247,6 +252,7 @@
$(sessionModel.recordingModel)
.on('startingRecording', function(e, data) {
displayStartingRecording();
lockControlsforJamTrackRecording();
})
.on('startedRecording', function(e, data) {
if(data.reason) {
@ -292,12 +298,30 @@
{
displayStartedRecording();
displayWhoCreated(data.clientId);
lockControlsforJamTrackRecording();
}
})
.on('stoppingRecording', function(e, data) {
displayStoppingRecording(data);
unlockControlsforJamTrackRecording();
})
.on('stoppedRecording', function(e, data) {
unlockControlsforJamTrackRecording();
if(sessionModel.selfOpenedJamTracks()) {
var timeline = context.jamClient.GetJamTrackTimeline();
rest.addRecordingTimeline(data.recordingId, timeline)
.fail(function(){
app.notify(
{ title: "Unable to Add JamTrack Volume Data",
text: "The volume of the JamTrack will not be correct in the recorded mix." },
null,
true);
})
}
if(data.reason) {
logger.warn("Recording Discarded: ", data);
var reason = data.reason;
@ -461,6 +485,14 @@
playbackControls.stopMonitor();
}
backing_track_path = currentSession == null ? null : currentSession.backing_track_path;
if(jamTrack == null && (currentSession && currentSession.jam_track != null)) {
playbackControls.startMonitor(true);
}
else if(jamTrack && (currentSession == null || currentSession.jam_track == null)) {
playbackControls.stopMonitor();
}
jamTrack = currentSession == null ? null : currentSession.jam_track;
}
function sessionChanged() {
@ -518,6 +550,14 @@
}
}
function resetOtherAudioContent() {
if ($('.session-recordings .track').length === 0 && $('.session-recordings .download-jamtrack').length === 0) {
$('.session-recordings .when-empty').show();
$('.session-recording-name-wrapper').hide();
$('.session-recordings .recording-controls').hide();
$('.session-recordings .session-recording-name').text('(No audio loaded)')
}
}
function didSelfOpenMedia() {
var localMediaMixers = _mixersForGroupIds([ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup], MIX_MODES.MASTER);
@ -530,7 +570,7 @@
}
function renderSession() {
$('#session-mytracks-container').empty();
$myTracksContainer.empty();
$('.session-track').remove(); // Remove previous tracks
var $voiceChat = $('#voice-chat');
$voiceChat.hide();
@ -545,7 +585,9 @@
if ($('.session-livetracks .track').length === 0) {
$('.session-livetracks .when-empty').show();
}
resetOtherAudioContent();
/**
if ($('.session-recordings .track').length === 0) {
$('.session-recordings .when-empty').show();
$('.session-recording-name-wrapper').hide();
@ -558,6 +600,7 @@
$('.session-recordings .recording-controls').show();
checkShowCloseControl();
}
*/
// Handle long labels:
$(".track-label").dotdotdot()
@ -1047,6 +1090,7 @@
// Default trackData to participant + no Mixer state.
var trackData = {
type: 'backing_track',
trackId: backingTrack.id,
clientId: backingTrack.client_id,
name: 'Backing',
@ -1101,19 +1145,14 @@
// find the track or tracks that correspond to the mixer
var correspondingTracks = []
$.each(jamTracks, function(i, jamTrack) {
if(mixer.id.indexOf("L") == 0) {
if(mixer.id.substring(1) == jamTrack.id) {
if(mixer.id == jamTrack.id) {
correspondingTracks.push(jamTrack);
}
else {
// this should not be possible
alert("Invalid state: the backing track had neither persisted_track_id or persisted_client_id");
}
}
});
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",
@ -1145,6 +1184,7 @@
// Default trackData to participant + no Mixer state.
var trackData = {
type: 'jam_track',
trackId: oneOfTheTracks.id,
clientId: oneOfTheTracks.client_id,
name: name,
@ -1254,6 +1294,7 @@
// Default trackData to participant + no Mixer state.
var trackData = {
type: 'metronome',
trackId: "MS" + oneOfTheTracks.id,
clientId: oneOfTheTracks.client_id,
name: "Metronome",
@ -1361,6 +1402,7 @@
// Default trackData to participant + no Mixer state.
var trackData = {
type: 'recorded_track',
trackId: oneOfTheTracks.id,
clientId: oneOfTheTracks.client_id,
name: name,
@ -1771,15 +1813,14 @@
function _addTrack(allowDelete, trackData, mixer, oppositeMixer) {
var parentSelector = '#session-mytracks-container';
var $destination = $(parentSelector);
var $destination = $myTracksContainer;
if (trackData.clientId !== app.clientId) {
parentSelector = '#session-livetracks-container';
$destination = $(parentSelector);
$destination = $liveTracksContainer
$('.session-livetracks .when-empty').hide();
}
var template = $('#template-session-track').html();
var newTrack = $(context.JK.fillTemplate(template, trackData));
newTrack.data('track_data', trackData)
var audioOverlay = $('.disabled-track-overlay', newTrack);
var $trackIconMute = newTrack.find('.track-icon-mute')
$trackIconMute.muteSelector().on(EVENTS.MUTE_SELECTED, trackMuteSelected)
@ -1789,7 +1830,7 @@
$destination.append(newTrack);
// Render VU meters and gain fader
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]';
var trackSelector = $destination.selector + ' .session-track[track-id="' + trackData.trackId + '"]';
var gainPercent = trackData.gainPercent || 0;
connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, trackData.group_id);
@ -1805,21 +1846,31 @@
tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId);
}
// something is being shown now in the other audio area
function otherAudioFilled() {
$('.session-recordings .when-empty').hide();
$('.session-recording-name-wrapper').show();
}
function _addRecordingTrack(trackData, mixer, oppositeMixer) {
otherAudioFilled();
$('.session-recordings .recording-controls').show();
var parentSelector = '#session-recordedtracks-container';
var $destination = $(parentSelector);
var template = $('#template-session-track').html();
var newTrack = $(context.JK.fillTemplate(template, trackData));
$destination.append(newTrack);
newTrack.data('track_data', trackData);
$otherAudioContainer.append(newTrack);
if(trackData.preMasteredClass) {
context.JK.helpBubble($('.track-instrument', newTrack), 'pre-processed-track', {}, {offsetParent: newTrack.closest('.content-body')});
}
// Render VU meters and gain fader
var trackSelector = parentSelector + ' .session-track[track-id="' + trackData.trackId + '"]';
var trackSelector = $otherAudioContainer.selector + ' .session-track[track-id="' + trackData.trackId + '"]';
var gainPercent = trackData.gainPercent || 0;
var $track = connectTrackToMixer(trackSelector, trackData, trackData.mixerId, gainPercent, null);
var $trackIconMute = $track.find('.track-icon-mute')
@ -2072,6 +2123,11 @@
return false;
}
if(sessionModel.areControlsLockedForJamTrackRecording() && $control.closest('.session-track').data('track_data').type == 'jam_track') {
context.JK.prodBubble($control, 'jamtrack-controls-disabled', {}, {positions:['bottom'], offsetParent: $control.closest('.screen')})
return false;
}
if($control.data('showHelpAboutMediaMixers')) {
if(!sessionModel.hasShownAudioMediaMixerHelp()) {
context.JK.prodBubble($control, 'volume-media-mixers', {}, {positions:['bottom'], offsetParent: $control.closest('.screen')})
@ -2079,6 +2135,8 @@
}
}
$.each(mixerIds, function(i,v) {
var mixerId = v;
// behavior: if this is the user's track in personal mode, then we mute the track globally
@ -2316,6 +2374,14 @@
$('#recording-status').text("Make a Recording");
}
function lockControlsforJamTrackRecording() {
sessionModel.lockControlsforJamTrackRecording();
}
function unlockControlsforJamTrackRecording() {
sessionModel.unlockControlsforJamTrackRecording();
}
function displayWhoCreated(clientId) {
if(app.clientId != clientId) { // don't show to creator
sessionModel.findUserBy({clientId: clientId})
@ -2382,7 +2448,69 @@
return false;
}
app.layout.showDialog('open-jam-track-dialog');
app.layout.showDialog('open-jam-track-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) {
// once the dialog is closed, see if the user has a jamtrack selected
if(!data.canceled && data.result.jamTrack) {
var jamTrack = data.result.jamTrack;
// hide 'other audio' placeholder
otherAudioFilled();
if(downloadJamTrack) {
// if there was one showing before somehow, destroy it.
logger.warn("destroying existing JamTrack")
downloadJamTrack.root.remove();
downloadJamTrack.destroy();
downloadJamTrack = null
}
downloadJamTrack = new context.JK.DownloadJamTrack(app, jamTrack, 'large');
// the widget indicates when it gets to any transition; we can hide it once it reaches completion
$(downloadJamTrack).on(EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED, function(e, data) {
if(data.state == downloadJamTrack.states.synchronized) {
logger.debug("jamtrack synchronized; hide widget and show tracks")
downloadJamTrack.root.remove();
downloadJamTrack.destroy();
downloadJamTrack = null;
// XXX: test with this removed; it should be unnecessary
context.jamClient.JamTrackStopPlay();
if(jamTrack.jmep)
{
logger.debug("setting jmep data")
context.jamClient.JamTrackLoadJmep(jamTrack.id, jamTrack.jmep)
}
else {
logger.debug("no jmep data for jamtrack")
}
// JamTrackPlay means 'load'
var result = context.jamClient.JamTrackPlay(jamTrack.id);
if(!result) {
app.notify(
{ title: "JamTrack Can Not Open",
text: "Unable to open your JamTrack. Please contact support@jamkazam.com"
}, null, true);
}
}
})
// show it on the page
$otherAudioContainer.append(downloadJamTrack.root)
// kick off the download JamTrack process
downloadJamTrack.init()
}
else {
logger.debug("OpenJamTrack dialog closed with no selection; ignoring", data)
}
})
return false;
}
@ -2497,7 +2625,7 @@
if(sessionModel.recordedTracks()) {
closeRecording();
}
else if(sessionModel.jamTracks()) {
else if(sessionModel.jamTracks() || downloadJamTrack) {
closeJamTrack();
}
else if(sessionModel.backingTrack() && sessionModel.backingTrack().path) {
@ -2533,6 +2661,20 @@
}
function closeJamTrack() {
logger.debug("closing recording");
if(downloadJamTrack) {
logger.debug("closing DownloadJamTrack widget")
downloadJamTrack.root.remove();
downloadJamTrack.destroy();
downloadJamTrack = null;
// this is necessary because a syncing widget means no jamtracks are loaded;
// so removing the widget will not cause a backend media change event (and so renderSession will not be called, ultimately)
resetOtherAudioContent();
}
rest.closeJamTrack({id: sessionModel.id()})
.done(function() {
sessionModel.refreshCurrentSession(true);
@ -2567,6 +2709,8 @@
}
function closeRecording() {
logger.debug("closing recording");
rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id})
.done(function(response) {
//sessionModel.refreshCurrentSession(true);
@ -2598,10 +2742,28 @@
function onChangePlayPosition(e, data){
logger.debug("calling jamClient.SessionTrackSeekMs(" + data.positionMs + ")");
if(data.jamTrackMode) {
context.jamClient.SessionJamTrackSeekMs(data.positionMs);
}
else {
context.jamClient.SessionTrackSeekMs(data.positionMs);
}
}
function startStopRecording() {
// check first if a jamtrack is loaded, and playing; if so, tell user to stop the play
if(sessionModel.jamTracks() && context.jamClient.isSessionTrackPlaying()) {
app.notify(
{ title: "Can't Recording a Play JamTrack",
text: "Stop the JamTrack before trying to recording." },
null,
true);
return;
}
if(sessionModel.recordingModel.isRecording()) {
sessionModel.recordingModel.stopRecording();
}
@ -2720,6 +2882,9 @@
$screen = $('#session-screen');
$mixModeDropdown = $screen.find('select.monitor-mode')
$templateMixerModeChange = $('#template-mixer-mode-change');
$otherAudioContainer = $('#session-recordedtracks-container');
$myTracksContainer = $('#session-mytracks-container')
$liveTracksContainer = $('#session-livetracks-container');
$closePlaybackRecording = $('#close-playback-recording')
$openBackingTrack = $('#open-a-backingtrack');
events();

View File

@ -37,6 +37,7 @@
var previousBackingTracks = [];
var openBackingTrack = null;
var shownAudioMediaMixerHelp = false;
var controlsLockedForJamTrackRecording = false;
var mixerMode = MIX_MODES.PERSONAL;
@ -113,6 +114,11 @@
}
}
// did I open up the current JamTrack?
function selfOpenedJamTracks() {
return currentSession && (currentSession.jam_track_initiator_id == context.JK.currentUserId)
}
function backingTrack() {
if(currentSession) {
// TODO: objectize this for VRFS-2665, VRFS-2666, VRFS-2667, VRFS-2668
@ -152,6 +158,18 @@
return inSession;
}
function lockControlsforJamTrackRecording() {
controlsLockedForJamTrackRecording = true;
}
function unlockControlsforJamTrackRecording() {
controlsLockedForJamTrackRecording = false;
}
function areControlsLockedForJamTrackRecording() {
return controlsLockedForJamTrackRecording;
}
function onMixerModeChanged(newMixerMode)
{
mixerMode = newMixerMode;
@ -352,6 +370,7 @@
previousBackingTracks = []
openBackingTrack = null
shownAudioMediaMixerHelp = false
controlsLockedForJamTrackRecording = false;
}
// you should only update currentSession with this function
@ -830,6 +849,10 @@
this.isMasterMixMode = isMasterMixMode;
this.isPersonalMixMode = isPersonalMixMode;
this.getMixMode = getMixMode;
this.selfOpenedJamTracks = selfOpenedJamTracks;
this.areControlsLockedForJamTrackRecording = areControlsLockedForJamTrackRecording;
this.lockControlsforJamTrackRecording = lockControlsforJamTrackRecording;
this.unlockControlsforJamTrackRecording = unlockControlsforJamTrackRecording;
// ALERT HANDLERS
this.onBackendMixerChanged = onBackendMixerChanged;

View File

@ -84,6 +84,7 @@ class SubscriptionUtils
# call subscribe, and use the returned object to listen for events of name context.JK.EVENTS.SUBSCRIBE_NOTIFICATION
subscribe: (type, id) =>
id = id.toString()
key = this.genKey(type, id)
@logger.debug("subscribing for any notifications for #{key}")
@ -104,6 +105,7 @@ class SubscriptionUtils
# TODO: this should not send a unsubscribe message to the server it's the last listener for the specific type/id combo
unsubscribe: (type, id) =>
id = id.toString()
key = this.genKey(type, id)
@logger.debug("unsubscribing for any notifications for #{key}")

View File

@ -910,6 +910,10 @@
return null;
}
context.JK.makeAbsolute = function(path) {
return window.location.protocol + '//' + window.location.host + path;
}
context.JK.popExternalLinks = function ($parent) {
if(!$parent) $parent = $('body');

View File

@ -168,6 +168,40 @@
.thanks-panel {
padding: 30px;
span.notice {
font-style:italic;
font-size:12px;
}
br.purchase-downloads {
clear:both;
margin-bottom:20px;
}
.thanks-detail.purchased-jam-track {
margin-top:20px;
.purchased-jam-track-header {
font-size: 15px;
margin-bottom:10px;
}
ul.purchased-list {
float:left;
margin:20px 100px 0 20px;
li {
margin:0;
}
}
.download-jamtrack {
width:auto;
vertical-align: middle; // to make bullets mid-align when error shows
}
}
}
.order-panel {

View File

@ -66,4 +66,5 @@
*= require jquery.Jcrop
*= require icheck/minimal/minimal
*= require users/syncViewer
*= require ./downloadJamTrack
*/

View File

@ -1,5 +1,77 @@
@import "client/common";
.download-jamtrack {
display:inline-block;
width:100%;
.retry-button {
margin-top:20px;
}
.retry {
margin-top:10px;
}
.msg {
white-space: normal;
}
.spinner-large {
margin:20px auto 0;
text-align:center;
}
.small {
.state {
text-align:left;
}
font-size:inherit;
.msg {
line-height: 32px;
height: 32px;
display: inline-block;
vertical-align: middle;
}
.spinner-small {
display:inline-block;
vertical-align:middle;
}
}
.large {
.state {
text-align:center;
}
}
&.active {
.small {
margin-bottom:5px;
.msg {
font-weight:bold;
color:white;
display:inline;
}
.errormsg {
display:block;
font-size:14px;
}
.retry {
display:block;
margin:3px 0 0 0;
font-size:14px;
}
.retry-button {
float:right;
margin:5px 0 5px 20px;
}
.msg-holder {
display:block;
}
}
}
}

View File

@ -148,6 +148,17 @@
}
}
.session-recording-name {
width:60%;
overflow:hidden;
margin-top:9px;
margin-bottom:8px;
font-size:16px;
}
.download-jamtrack {
margin-top:50px;
}
}

View File

@ -58,14 +58,21 @@ class ApiJamTracksController < ApiController
end
def keys
jamtrack_ids = params[:jamtracks]
jamtrack_holder = params[:jamtracks]
unless jamtrack_ids.kind_of?(Array)
render :json => {message: 'jamtracks parameter must be an array'}, :status => 200
unless jamtrack_holder.kind_of?(Hash)
render :json => {message: 'jamtracks parameter must be an hash'}, :status => 422
return
end
@jam_tracks = JamTrackRight.list_keys(current_user, params[:jamtracks])
jamtrack_ids = jamtrack_holder[:tracks]
unless jamtrack_ids.kind_of?(Array)
render :json => {message: 'jamtracks:tracks parameter must be an array'}, :status => 422
return
end
@jam_tracks = JamTrackRight.list_keys(current_user, jamtrack_ids)
render "api_jam_tracks/list_keys", :layout => nil
end

View File

@ -1,7 +1,7 @@
class ApiRecordingsController < ApiController
before_filter :api_signed_in_user, :except => [ :add_like ]
before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim ]
before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim, :add_timeline_data ]
before_filter :lookup_recorded_track, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ]
before_filter :lookup_recorded_backing_track, :only => [ :backing_track_download, :backing_track_upload_next_part, :backing_track_upload_sign, :backing_track_upload_part_complete, :backing_track_upload_complete ]
before_filter :lookup_recorded_video, :only => [ :video_upload_sign, :video_upload_start, :video_upload_complete ]
@ -377,6 +377,13 @@ class ApiRecordingsController < ApiController
end
end
# metadata
def add_timeline
@recording.add_timeline(params[:metadata])
render :json => {}, :status => 200
end
private
def lookup_recording

View File

@ -57,20 +57,36 @@ class ApiRecurlyController < ApiController
def place_order
error=nil
puts "PLACING ORDER #{params.inspect}"
response = {jam_tracks:[]}
# 1st confirm that all specified JamTracks exist
jam_tracks = []
params[:jam_tracks].each do |jam_track_id|
jam_track = JamTrack.where("id=?", jam_track_id).first
if jam_track
@client.place_order(current_user, jam_track)
jam_tracks << jam_track
else
error="JamTrack not found for '#{jam_track_id}'"
break
end
end
# then buy each
unless error
jam_tracks.each do |jam_track|
jam_track_right = @client.place_order(current_user, jam_track)
# build up the response object with JamTracks that were purchased.
# if this gets more complicated, we should switch to RABL
response[:jam_tracks] << {name: jam_track.name, id: jam_track.id, jam_track_right_id: jam_track_right.id}
end
end
if error
render json: { errors: {message:error}}, :status => 404
else
render :json=>{}, :status=>200
render :json=>response, :status=>200
end
rescue RecurlyClientError => x
render json: { message: x.inspect, errors: x.errors}, :status => 404

View File

@ -33,11 +33,26 @@ class SpikesController < ApplicationController
def subscription
#Notification.send_reload(MessageFactory::ALL_NATIVE_CLIENTS)
Notification.send_subscription_message('test', '1', '{"msg": "oh hai 1"}')
Notification.send_subscription_message('test', '2', '{"msg": "oh hai 2"}')
render text: 'oh hai'
end
def download_jam_track
jamTrack = JamTrack.find(params[:jam_track_id])
jamTrackRight = jamTrack.right_for_user(current_user)
gon.jamTrackId = jamTrack.id
gon.jamTrackRightId = jamTrackRight.id
gon.size = params[:size] ? params[:size] : 'large'
gon.switchState = params[:state]
render :layout => 'web'
end
def site_validate
render :layout => 'web'
end
@ -45,5 +60,4 @@ class SpikesController < ApplicationController
def recording_source
render :layout => 'web'
end
end

View File

@ -20,8 +20,16 @@ node :mix do |claimed_recording|
end
child(:recording => :recording) {
attributes :id, :created_at, :duration, :comment_count, :like_count, :play_count
attributes :id, :created_at, :duration, :comment_count, :like_count, :play_count, :jam_track_id, :jam_track_initiator_id
child(:jam_track => :jam_track) {
attributes :id
node :jmep do |jam_track|
jam_track.jmep_json ? JSON.parse(jam_track.jmep_json) : nil
end
}
child(:band => :band) {
attributes :id, :name, :location, :photo_url
}
@ -50,6 +58,15 @@ child(:recording => :recording) {
end
}
child(:recorded_jam_track_tracks => :recorded_jam_track_tracks) {
node do |recorded_jam_track_track|
{
id: recorded_jam_track_track.jam_track_track.id,
timeline: recorded_jam_track_track.timeline ? JSON.parse(recorded_jam_track_track.timeline) : []
}
end
}
child(:comments => :comments) {
attributes :comment, :created_at

View File

@ -2,7 +2,7 @@ object @jam_tracks
node do |jam_track|
{
id: jam_track['id'],
id: jam_track['id'].to_s,
private: jam_track['private_key'],
error: jam_track['private_key'] ? nil : ( jam_track['jam_track_right_id'] ? 'no_key' : 'not_purchased' )
}

View File

@ -1,6 +1,6 @@
object @jam_track
attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price
attributes :id, :name, :description, :recording_type, :original_artist, :songwriter, :publisher, :sales_region, :price, :version
node :genres do |item|
[item.genre.description] # XXX: need to return single genre; not array

View File

@ -1,6 +1,14 @@
object @jam_track
attributes :id, :name, :description, :initial_play_silence, :original_artist
attributes :id, :name, :description, :initial_play_silence, :original_artist, :version
node :jmep do |jam_track|
jam_track.jmep_json ? JSON.parse(jam_track.jmep_json) : nil
end
node :jam_track_right_id do |jam_track|
jam_track.right_for_user(current_user).id
end
child(:jam_track_tracks => :tracks) {
attributes :id, :part, :instrument

View File

@ -13,7 +13,7 @@ if !current_user
}
else
attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active
attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active, :jam_track_initiator_id
node :can_join do |session|
session.can_join?(current_user, true)

View File

@ -1,6 +1,6 @@
object @recording
attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :when_will_be_discarded?
attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :when_will_be_discarded?, :jam_track_id, :jam_track_initiator_id
node :mix do |recording|
if recording.mix
@ -12,6 +12,13 @@ node :mix do |recording|
end
end
child(:jam_track => :jam_track) {
attributes :id
node :jmep do |jam_track|
jam_track.jmep_json ? JSON.parse(jam_track.jmep_json) : nil
end
}
child(:band => :band) {
attributes :id, :name, :location, :photo_url
@ -33,6 +40,15 @@ child(:recorded_backing_tracks => :recorded_backing_tracks) {
end
}
child(:recorded_jam_track_tracks => :recorded_jam_track_tracks) {
node do |recorded_jam_track_track|
{
id: recorded_jam_track_track.jam_track_track.id,
timeline: recorded_jam_track_track.timeline ? JSON.parse(recorded_jam_track_track.timeline) : []
}
end
}
child(:comments => :comments) {
attributes :comment, :created_at

View File

@ -1,2 +1,86 @@
script type="text/template" id='template-download-jamtrack'
.download-jamtrack
.state
script type="text/template" id="template-download-jamtrack-state-no-client"
.state-no-client
.large.hidden
.msg
| To play your JamTrack, launch the JamKazam application and open the JamTrack while in a session.
.small.hidden
.msg
| {{data.name}} (launch client)
script type="text/template" id="template-download-jamtrack-state-synchronized"
.state-synchronized
.large.hidden
.msg
| Your JamTrack is on your system and ready to play.
.small.hidden
.msg
| {{data.name}} (done)
script type="text/template" id="template-download-jamtrack-state-packaging"
.state-packaging
.large.hidden
.msg
| Your JamTrack is currently being created on the JamKazam server.
.spinner-large
.small.hidden
.msg
| {{data.name}} (packaging)
.spinner-small
script type="text/template" id="template-download-jamtrack-state-downloading"
.state-downloading
.large.hidden
.msg
| Your JamTrack is currently being downloaded.
.spinner-large
.small.hidden
.msg
| {{data.name}} (downloading)
.spinner-small
script type="text/template" id="template-download-jamtrack-state-keying"
.state-keying
.large.hidden
.msg
| Your JamTrack is being authenticated.
.spinner-large
.small.hidden
.msg
| {{data.name}} (keying)
.spinner-small
script type="text/template" id="template-download-jamtrack-state-initial"
.state-initial
.large.hidden
.msg
| Initializing JamTrack...
.spinner-large
.small.hidden
.msg
| {{data.name}} (initializing)
.spinner-small
script type="text/template" id="template-download-jamtrack-state-quiet"
.state-quiet
.large.hidden
.msg
.small.hidden
.msg
| {{data.name}} (pending)
script type="text/template" id="template-download-jamtrack-state-errored"
.state-errored
.large.hidden
.msg
.retry
a.button-orange.retry-button RETRY
.small.hidden
.msg-holder
.msg
a.button-orange.retry-button RETRY
.errormsg
.retry

View File

@ -199,6 +199,9 @@ script type="text/template" id="template-help-media-controls-disabled"
| Only the person who opened the recording can control the volume levels.
| {% } %}
script type="text/template" id="template-help-jamtrack-controls-disabled"
| During a recording, volume and mute controls for JamTracks are disabled. So, get the session volume levels right before starting the recording.
script type="text/template" id="template-help-volume-media-mixers"
| Audio files only expose both master and personal mix controls, so any change here will also affect everyone in the session.

View File

@ -178,8 +178,15 @@ div layout="screen" layout-id="order" id="orderScreen" class="screen secondary"
br
.thanks-detail We'll send you an email confirming your order shortly.
br
.thanks-detail If you purchased any JamTracks, the next time you run the JamKazam application, your JamTracks will automatically be downloaded to the app, and you will receive a notification when the download is complete.
.thanks-detail.jam-tracks-in-browser.hidden
| To play your purchased JamTrack, launch the JamKazam application and open the JamTrack while in a session.
.thanks-detail.purchased-jam-track.hidden
h2.purchased-jam-track-header Downloading Your Purchased JamTracks
span Each JamTrack will be downloaded sequentially.
br
span.notice Note that you do not have to wait for this to complete in order to use your JamTrack later.
br.clear
ul.purchased-list
@ -276,4 +283,7 @@ script type='text/template' id='template-order-content'
span and
'
a href="http://www.jamkazam.com/corp/returns" returns policy
span .
span .
script type='text/template' id='template-purchased-jam-track'
li data-jam-track-id="{{data.jam_track_id}}"

View File

@ -131,37 +131,47 @@
</div>
<br clear="all" />
<div><a href="/client#/account/profile" class="add-recordings">Add Recordings</a></div>
<br clear="all" />
<div class="section-header">Online Presence</div>
<br clear="all" />
<div id="no-online-presence" class="left">None specified</div>
<div id="user-website">
<div id="user-website" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<div id="soundcloud-presence">
<div id="soundcloud-presence" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<div id="reverbnation-presence">
<div id="reverbnation-presence" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<div id="bandcamp-presence">
<div id="bandcamp-presence" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<div id="fandalism-presence">
<div id="fandalism-presence" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<div id="youtube-presence">
<div id="youtube-presence" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<div id="facebook-presence">
<div id="facebook-presence" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<div id="twitter-presence">
<div id="twitter-presence" class="left">
<a rel="external"><img src="/assets/header/logo.png" width="120" height="30" /></a>
</div>
<br clear="all" />
<div><a href="/client#/account/profile" class="add-sites">Add Sites</a></div>
<br clear="all" />
<div class="section-header">Current Interests</div>
@ -219,7 +229,7 @@
</div>
</div>
<br clear="all" />
<div><a href="/client#/account/profile" class="add-interests">Add Interests</a></div>
<div id="virtual-band">
<div class="left profile-details">I'm interested in forming virtual band(s)</div>

View File

@ -71,7 +71,7 @@
| other audio
.session-recording-name-wrapper
.session-recording-name.left
| (No recording loaded)
| (No audio loaded)
.session-add.right
a#close-playback-recording[href="#"]
= image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"}

View File

@ -64,6 +64,7 @@
<%= render "overlay_small" %>
<%= render "listenBroadcast" %>
<%= render "sync_viewer_templates" %>
<%= render "download_jamtrack_templates" %>
<%= render "help" %>
<%= render 'dialogs/dialogs' %>
<div id="fb-root"></div>

View File

@ -31,7 +31,7 @@
a href='/client#/jamtrack' rel="external"
| Shop for JamTracks
.right
a href="#" class="button-grey" layout-action="close"
a href="#" class="button-grey" layout-action="cancel"
| CANCEL

View File

@ -0,0 +1,44 @@
= javascript_include_tag "download_jamtrack"
= render "clients/download_jamtrack_templates"
= stylesheet_link_tag "client/downloadJamTrack"
- provide(:title, 'Download Jam Track Widget')
.content-wrapper
h2 Jam Track State Widget
h3 Possible States
ul
li synchronized
li no_client
li packaging
li downloading
li keying
li initial
li errored
#widget
javascript:
var initialized = false;
$(document).on('JAMKAZAM_READY', function(e, data) {
window.JK.JamServer.get$Server().on(window.JK.EVENTS.CONNECTION_UP, function() {
if(initialized) {
return;
}
initialized = true
setTimeout(function() {
window.downloadJamTrack = new JK.DownloadJamTrack(data.app, {id: gon.jamTrackId, jam_track_right_id: gon.jamTrackRightId, name: 'Back in Black'}, gon.size)
downloadJamTrack.init()
$('#widget').append(window.downloadJamTrack.root)
if (gon.switchState == 'errored') {
downloadJamTrack.transitionError("package-error", "The server failed to create your package.")
}
else if (gon.switchState) {
downloadJamTrack.transition(downloadJamTrack.states[gon.switchState]);
}
}, 1)
})
})

View File

@ -209,6 +209,8 @@ if defined?(Bundler)
# Location of jamtracks python tool:
config.jamtracks_dir = ENV['JAMTRACKS_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jamtracks"))
config.jmep_dir = ENV['JMEP_DIR'] || File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "jmep"))
# amount of time before we think packaging job is broken
config.signing_job_run_max_time = 60 # 1 minute
# amount of time before we think the queue is stuck
@ -310,6 +312,7 @@ if defined?(Bundler)
config.influxdb_port = 8086
config.influxdb_ignored_environments = ENV["INFLUXDB_ENABLED"] == '1' ? ['test', 'cucumber'] : ['test', 'cucumber', 'development']
config.allow_spikes = false
config.show_jamblaster_notice = true
config.show_jamblaster_kickstarter_link = true
config.metronome_available = true

View File

@ -81,21 +81,20 @@ SampleApp::Application.routes.draw do
match '/endorse/:id/:service', to: 'users#endorse', :as => 'endorse'
# temporarily allow for debugging--only allows admini n
match '/listen_in', to: 'spikes#listen_in'
# embed resque-web if this is development mode
if Rails.env == "development"
if Rails.env == "development" || Rails.application.config.allow_spikes
require 'resque/server'
require 'resque-retry'
require 'resque-retry/server'
mount Resque::Server.new, :at => "/resque" if Rails.env == "development"
# route to spike controller (proof-of-concepts)
match '/listen_in', to: 'spikes#listen_in'
match '/facebook_invite', to: 'spikes#facebook_invite'
match '/launch_app', to: 'spikes#launch_app'
match '/websocket', to: 'spikes#websocket'
match '/test_subscription', to: 'spikes#subscription'
match '/widgets/download_jam_track', to: 'spikes#download_jam_track'
match '/site_validate', to: 'spikes#site_validate'
match '/recording_source', to: 'spikes#recording_source'
@ -210,7 +209,7 @@ SampleApp::Application.routes.draw do
match '/jamtracks/downloads' => 'api_jam_tracks#downloads', :via => :get, :as => 'api_jam_tracks_downloads'
match '/jamtracks/download/:id' => 'api_jam_tracks#download', :via => :get, :as => 'api_jam_tracks_download'
match '/jamtracks/enqueue/:id' => 'api_jam_tracks#enqueue', :via => :post, :as => 'api_jam_tracks_enqueue'
match '/jamtracks/show/:id' => 'api_jam_tracks#show_jam_track_right', :via => :get, :as => 'api_jam_tracks_show_right'
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'
# Shopping carts
@ -456,6 +455,7 @@ SampleApp::Application.routes.draw do
match '/recordings/:id/comments' => 'api_recordings#add_comment', :via => :post, :as => 'api_recordings_add_comment'
match '/recordings/:id/likes' => 'api_recordings#add_like', :via => :post, :as => 'api_recordings_add_like'
match '/recordings/:id/discard' => 'api_recordings#discard', :via => :post, :as => 'api_recordings_discard'
match '/recordings/:id/timeline' => 'api_recordings#add_timeline', :via => :post, :as => 'api_recordings_timeline'
# Recordings - recorded_tracks
match '/recordings/:id/tracks/:track_id' => 'api_recordings#show_recorded_track', :via => :get, :as => 'api_recordings_show_recorded_track'

View File

@ -1,6 +1,6 @@
require 'factory_girl'
require 'open-uri'
#require './spec/factories.rb' # useful when run on a server
namespace :db do
desc "Add a simple one track recording to the database"

View File

@ -101,6 +101,7 @@ describe ApiJamTracksController do
response.should be_success
json = JSON.parse(response.body)
json['jamtracks'].length.should eq(1)
json['jamtracks'][0]['jam_track_right_id'].should eq(right.id)
json['next'].should be_nil
end
end
@ -157,18 +158,18 @@ describe ApiJamTracksController do
describe "keys" do
it "empty" do
get :keys, jamtracks: []
get :keys, jamtracks: {tracks: []}
response.status.should == 200
json = JSON.parse(response.body)
json.length == 0
end
it "track with no rights" do
get :keys, jamtracks: [@jam_track.id]
get :keys, jamtracks: { tracks: [@jam_track.id] }
response.status.should == 200
json = JSON.parse(response.body)
json.length.should == 1
json[0]['id'].should == @jam_track.id
json[0]['id'].should == @jam_track.id.to_s
json[0]['private'].should be_nil
json[0]['error'].should == 'not_purchased'
end
@ -177,32 +178,32 @@ describe ApiJamTracksController do
right = FactoryGirl.create(:jam_track_right, user: @user, private_key: nil, jam_track: @jam_track)
get :keys, jamtracks: [@jam_track.id]
get :keys, jamtracks: { tracks: [@jam_track.id] }
response.status.should == 200
json = JSON.parse(response.body)
json.length.should == 1
json[0]['id'].should == @jam_track.id
json[0]['id'].should == @jam_track.id.to_s
json[0]['private'].should be_nil
json[0]['error'].should == 'no_key'
end
it "track with key" do
right = FactoryGirl.create(:jam_track_right, user: @user, private_key: 'abc', jam_track: @jam_track)
get :keys, jamtracks: [@jam_track.id]
get :keys, jamtracks: { tracks: [@jam_track.id] }
response.status.should == 200
json = JSON.parse(response.body)
json.length.should == 1
json[0]['id'].should == @jam_track.id
json[0]['id'].should == @jam_track.id.to_s
json[0]['private'].should eq('abc')
json[0]['error'].should be_nil
end
it "non-owning user asking for a real track" do
right = FactoryGirl.create(:jam_track_right, user: FactoryGirl.create(:user), private_key: 'abc', jam_track: @jam_track)
get :keys, jamtracks: [@jam_track.id]
get :keys, jamtracks: { tracks: [@jam_track.id] }
response.status.should == 200
json = JSON.parse(response.body)
json[0]['id'].should == @jam_track.id
json[0]['id'].should == @jam_track.id.to_s
json[0]['private'].should be_nil
json[0]['error'].should == 'not_purchased'
end

View File

@ -3,217 +3,251 @@ require 'spec_helper'
describe ApiRecordingsController do
render_views
describe "recording with backing track" do
before(:each) do
@user = FactoryGirl.create(:user)
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@backing_track = FactoryGirl.create(:backing_track, :connection => @connection)
controller.current_user = @user
end
describe "start" do
it "should work" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should_not be_nil
recording = Recording.find(response_body['id'])
before(:each) do
@user = FactoryGirl.create(:user)
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@backing_track = FactoryGirl.create(:backing_track, :connection => @connection)
controller.current_user = @user
end
it "should not allow multiple starts" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
post :start, { :format => 'json', :music_session_id => @music_session.id }
response.status.should == 422
response_body = JSON.parse(response.body)
response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_BEING_RECORDED
describe "start" do
it "should work" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should_not be_nil
recording = Recording.find(response_body['id'])
end
it "should not allow multiple starts" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
post :start, {:format => 'json', :music_session_id => @music_session.id}
response.status.should == 422
response_body = JSON.parse(response.body)
response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_BEING_RECORDED
end
it "should not allow start while playback ongoing" do
recording = Recording.start(@music_session, @user)
recording.stop
recording.reload
claimed_recording = recording.claim(@user, "name", "description", Genre.first, true)
@music_session.claimed_recording_start(@user, claimed_recording)
@music_session.errors.any?.should be_false
post :start, {:format => 'json', :music_session_id => @music_session.id}
response.status.should == 422
response_body = JSON.parse(response.body)
response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_PLAYBACK_RECORDING
end
it "should not allow start by somebody not in the music session" do
user2 = FactoryGirl.create(:user)
controller.current_user = user2
post :start, {:format => 'json', :music_session_id => @music_session.id}
response.status.should == 403
end
end
it "should not allow start while playback ongoing" do
recording = Recording.start(@music_session, @user)
recording.stop
recording.reload
claimed_recording = recording.claim(@user, "name", "description", Genre.first, true)
@music_session.claimed_recording_start(@user, claimed_recording)
@music_session.errors.any?.should be_false
post :start, { :format => 'json', :music_session_id => @music_session.id }
response.status.should == 422
response_body = JSON.parse(response.body)
response_body["errors"]["music_session"][0].should == ValidationMessages::ALREADY_PLAYBACK_RECORDING
describe "get" do
it "should work" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should_not be_nil
recordingId = response_body['id']
get :show, {:format => 'json', :id => recordingId}
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should == recordingId
end
end
it "should not allow start by somebody not in the music session" do
user2 = FactoryGirl.create(:user)
controller.current_user = user2
post :start, { :format => 'json', :music_session_id => @music_session.id }
response.status.should == 403
describe "stop" do
it "should work" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, {:format => 'json', :id => recording.id}
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should_not be_nil
Recording.find(response_body['id']).id.should == recording.id
end
it "should not allow stop on a session not being recorded" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, {:format => 'json', :id => recording.id}
post :stop, {:format => 'json', :id => recording.id}
response.status.should == 422
response_body = JSON.parse(response.body)
end
it "should not allow stop on a session requested by a different member" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
user2 = FactoryGirl.create(:user)
controller.current_user = user2
post :stop, {:format => 'json', :id => recording.id}
response.status.should == 403
end
end
describe "download track" do
let(:mix) { FactoryGirl.create(:mix) }
it "should only allow a user to download a track if they have claimed the recording" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, {:format => 'json', :id => recording.id}
response.should be_success
end
it "is possible" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
controller.current_user = mix.recording.owner
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 1
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 2
end
it "prevents download after limit is reached" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
controller.current_user = recorded_track.user
get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 404
JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed"
end
it "lets admins surpass limit" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
recorded_track.user.admin = true
recorded_track.user.save!
controller.current_user = recorded_track.user
get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 101
end
end
describe "download backing track" do
let(:mix) { FactoryGirl.create(:mix) }
it "should only allow a user to download a track if they have claimed the recording" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, {:format => 'json', :id => recording.id}
response.should be_success
end
it "is possible" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
controller.current_user = mix.recording.owner
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 1
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 2
end
it "prevents download after limit is reached" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
controller.current_user = recorded_track.user
get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 404
JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed"
end
it "lets admins surpass limit" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
recorded_track.user.admin = true
recorded_track.user.save!
controller.current_user = recorded_track.user
get :download, {format: 'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 101
end
end
end
describe "get" do
it "should work" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should_not be_nil
recordingId = response_body['id']
get :show, {:format => 'json', :id => recordingId}
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should == recordingId
describe "recording with jam track" do
before(:each) do
@user = FactoryGirl.create(:user)
@instrument = FactoryGirl.create(:instrument, :description => 'a great instrument')
@music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true)
@connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session)
@track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument)
@jam_track = FactoryGirl.create(:jam_track)
# make sure the jam track is opened
@music_session.jam_track = @jam_track
@music_session.jam_track_initiator = @user
@music_session.save!
controller.current_user = @user
end
end
describe "stop" do
it "should work" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, { :format => 'json', :id => recording.id }
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should_not be_nil
Recording.find(response_body['id']).id.should == recording.id
end
it "should not allow stop on a session not being recorded" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, { :format => 'json', :id => recording.id }
post :stop, { :format => 'json', :id => recording.id }
response.status.should == 422
response_body = JSON.parse(response.body)
end
it "should not allow stop on a session requested by a different member" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
user2 = FactoryGirl.create(:user)
controller.current_user = user2
post :stop, { :format => 'json', :id => recording.id }
response.status.should == 403
end
end
describe "download track" do
let(:mix) { FactoryGirl.create(:mix) }
it "should only allow a user to download a track if they have claimed the recording" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, { :format => 'json', :id => recording.id }
response.should be_success
end
it "is possible" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
controller.current_user = mix.recording.owner
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 1
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 2
end
it "prevents download after limit is reached" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
controller.current_user = recorded_track.user
get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 404
JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed"
end
it "lets admins surpass limit" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
recorded_track.user.admin = true
recorded_track.user.save!
controller.current_user = recorded_track.user
get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 101
end
end
describe "download backing track" do
let(:mix) { FactoryGirl.create(:mix) }
it "should only allow a user to download a track if they have claimed the recording" do
post :start, { :format => 'json', :music_session_id => @music_session.id }
response_body = JSON.parse(response.body)
recording = Recording.find(response_body['id'])
post :stop, { :format => 'json', :id => recording.id }
response.should be_success
end
it "is possible" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
controller.current_user = mix.recording.owner
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 1
get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 2
end
it "prevents download after limit is reached" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
controller.current_user = recorded_track.user
get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 404
JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed"
end
it "lets admins surpass limit" do
mix.touch
recorded_track = mix.recording.recorded_tracks[0]
recorded_track.download_count = APP_CONFIG.max_audio_downloads
recorded_track.save!
recorded_track.user.admin = true
recorded_track.user.save!
controller.current_user = recorded_track.user
get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id}
response.status.should == 302
recorded_track.reload
recorded_track.download_count.should == 101
describe "start" do
it "should work" do
post :start, {:format => 'json', :music_session_id => @music_session.id}
response.should be_success
response_body = JSON.parse(response.body)
response_body['id'].should_not be_nil
recording = Recording.find(response_body['id'])
recorded_jam_track_track = response_body["recorded_jam_track_tracks"][0]
recorded_jam_track_track["id"].should eq(@jam_track.jam_track_tracks[0].id)
recorded_jam_track_track["timeline"].should eq([])
end
end
end
end

View File

@ -1,16 +1,310 @@
describe "DownloadJamTrack", ->
beforeEach ->
this.fixtures = fixture.load("downoadJamTrack.html", "user_sync_track1.json"); # append these fixtures which were already cached
this.server = sinon.fakeServer.create();
this.fixtures = fixture.load("downloadJamTrack.html"); # append these fixtures which were already cached
window.jamClient = sinon.stub()
this.downloadJamTrack = new JK.DownloadJamTrack()
this.downloadJamTrack.init()
$('body').append(this.downloadJamTrack.root)
this.app = sinon.stub()
this.jamTrackId = '1'
this.jamTrack = {id: this.jamTrackId, jam_track_right_id: '1', name: 'Back in Black', version:'1'}
window.gon = {}
window.JK.JamServer = {}
window.stats = {}
@statsSpy = window.stats.write = sinon.spy()
window.JK.JamServer.send = sinon.stub(); # attempts to subscribe to the socket will need this
afterEach ->
this.server.restore();
window.stats.write.reset()
describe "normal browser", ->
beforeEach ->
window.gon.isNativeClient = false
@showNoClientSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showNoClient')
@downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack)
$('body').append(this.downloadJamTrack.root)
afterEach ->
@showNoClientSpy.restore()
it "switches to 'no client' correctly", ->
window.jamClient.JamTrackGetTrackDetail = sinon.stub()
window.jamClient.InvalidateJamTrack = sinon.stub()
#window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready'})
@downloadJamTrack.init();
expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(0)
expect(@showNoClientSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.stateHolder.find('.state-no-client')).toHaveLength(1)
expect(@statsSpy.calledOnce).toBe(true)
describe "client", ->
beforeEach ->
window.gon.isNativeClient = true
describe "already synchronized", ->
beforeEach ->
@showSynchronizedSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showSynchronized')
@downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack)
$('body').append(@downloadJamTrack.root)
afterEach ->
@showSynchronizedSpy.restore()
@downloadJamTrack.destroy()
it "shows synchronized state", ->
window.jamClient.JamTrackGetTrackDetail = sinon.stub()
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
@downloadJamTrack.init();
expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1)
expect(window.jamClient.InvalidateJamTrack.callCount).toBe(0)
expect(@showSynchronizedSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.transitionTimer).toBe(null)
expect(@downloadJamTrack.downloadTimer).toBe(null)
expect(@statsSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.stateHolder.find('.state-synchronized')).toHaveLength(1)
describe "pending", ->
beforeEach ->
window.jamClient.JamTrackKeysRequest = sinon.stub()
@showSynchronizedSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showSynchronized')
@showErrorSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showError')
@showKeyingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showKeying')
@downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack)
@downloadJamTrack.states.keying.max_time = -1 # hurry up the test, instead of waiting 10 seconds
$('body').append(@downloadJamTrack.root)
afterEach ->
@showSynchronizedSpy.restore()
@showErrorSpy.restore()
@showKeyingSpy.restore()
@downloadJamTrack.destroy()
it "shows errored state due to timeout", ->
window.jamClient.JamTrackGetTrackDetail = sinon.stub()
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
@downloadJamTrack.init();
expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1)
expect(@showKeyingSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.states.keying.timer).toNotBe(null)
@downloadJamTrack.stateIntervalCheck()
expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(2)
expect(@showErrorSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.states.keying.timer).toBe(null)
expect(@downloadJamTrack.stateHolder.find('.state-errored')).toHaveLength(1)
expect(@downloadJamTrack.stateHolder.find('.state-errored .msg')).toContainText('It took too long for the JamTrack to be keyed.')
expect(@statsSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.attempts).toBe(1)
# now simulate a retry attempt
@downloadJamTrack.stateHolder.find('.retry-button').trigger('click')
# and verify that we are beginning a re-attempt
expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(3)
expect(@showKeyingSpy.calledTwice).toBe(true)
expect(@downloadJamTrack.states.keying.timer).toNotBe(null)
expect(@downloadJamTrack.attempts).toBe(2)
it "shows synchronized", ->
window.jamClient.JamTrackGetTrackDetail = sinon.stub()
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'pending', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
@downloadJamTrack.init()
expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1)
expect(@showKeyingSpy.calledOnce).toBe(true)
# keying timer should be firing
expect(@downloadJamTrack.states.keying.timer).toNotBe(null)
# say the keys have been fetched
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
# then do a check
@downloadJamTrack.stateIntervalCheck()
expect(@showSynchronizedSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.states.keying.timer).toBe(null)
expect(@downloadJamTrack.stateHolder.find('.state-synchronized')).toHaveLength(1)
expect(@statsSpy.calledOnce).toBe(true)
describe "JamTrack needs downloading", ->
beforeEach ->
window.jamClient.JamTrackDownload = sinon.stub()
@showSynchronizedSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showSynchronized')
@showErrorSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showError')
@showKeyingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showKeying')
@showDownloadingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showDownloading')
@showPackagingSpy = sinon.spy(JK.DownloadJamTrack.prototype, 'showPackaging')
@downloadJamTrack = new JK.DownloadJamTrack(@app, @jamTrack)
@downloadJamTrack.states.keying.max_time = -1 # hurry up the test, instead of waiting 10 seconds
$('body').append(@downloadJamTrack.root)
afterEach ->
@showSynchronizedSpy.restore()
@showErrorSpy.restore()
@showKeyingSpy.restore()
@showDownloadingSpy.restore()
@showPackagingSpy.restore()
@downloadJamTrack.destroy()
it "shows downloading for signed package", ->
window.jamClient.JamTrackKeysRequest = sinon.stub()
window.jamClient.JamTrackGetTrackDetail = sinon.stub()
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
spyOn(@downloadJamTrack.rest, 'getJamTrackRight').andCallFake((data) =>
d = $.Deferred();
d.resolve({signing_state: 'SIGNED'});
d.promise();
)
window.jamClient.JamTrackDownload = sinon.stub()
@downloadJamTrack.init()
expect(window.jamClient.JamTrackGetTrackDetail.callCount).toBe(1)
expect(window.jamClient.JamTrackDownload.callCount).toBe(1)
expect(@showDownloadingSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.stateHolder.find('.state-downloading')).toHaveLength(1)
eval(@downloadJamTrack.makeDownloadSuccessCallback() + '()')
expect(@showKeyingSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.stateHolder.find('.state-keying')).toHaveLength(1)
# keying timer should be firing
expect(@downloadJamTrack.states.keying.timer).toNotBe(null)
# say the keys have been fetched
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'ready', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
# check state again
@downloadJamTrack.stateIntervalCheck()
# we should now be synchronized
expect(@showSynchronizedSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.states.keying.timer).toBe(null)
expect(@downloadJamTrack.stateHolder.find('.state-synchronized')).toHaveLength(1)
expect(@statsSpy.calledOnce).toBe(true)
it "is not yet packaged", ->
window.jamClient.JamTrackGetTrackDetail = sinon.stub()
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
spyOn(@downloadJamTrack.rest, 'getJamTrackRight').andCallFake((data) =>
d = $.Deferred();
d.resolve({signing_state: 'QUIET'});
d.promise();
)
spyOn(@downloadJamTrack.rest, 'enqueueJamTrack').andCallFake((data) =>
d = $.Deferred();
d.resolve({});
d.promise();
)
window.jamClient.JamTrackDownload = sinon.stub()
@downloadJamTrack.init()
expect(@downloadJamTrack.attemptedEnqueue).toBe(true)
expect(@downloadJamTrack.transitionTimer?).toBe(true)
# simulate poke from server saying the track has been queued
@downloadJamTrack.onJamTrackRightEvent(null, {body: {signing_state: 'QUEUED'}})
# the frontend should be saying that it's packaging now
expect(@downloadJamTrack.transitionTimer?).toBe(true)
expect(@showPackagingSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.stateHolder.find('.state-packaging')).toHaveLength(1)
# simulate poke from server saying the track is currently signing
@downloadJamTrack.onJamTrackRightEvent(null, {body: {signing_state: 'SIGNING'}})
# the frontend still be saying it's packaging
expect(@downloadJamTrack.transitionTimer?).toBe(true)
expect(@showPackagingSpy.calledOnce).toBe(true)
# simulate poke from server saying the track is signed
@downloadJamTrack.onJamTrackRightEvent(null, {body: {signing_state: 'SIGNED'}})
expect(@downloadJamTrack.transitionTimer?).toBe(false)
# downloading has started; other test covers this, so we stop testing
expect(@showDownloadingSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.stateHolder.find('.state-downloading')).toHaveLength(1)
# since we haven't yet made it to a leave node, make sure we haven't reported a stat
expect(@statsSpy.callCount).toBe(0)
it "queue time out when packaging", ->
window.jamClient.JamTrackGetTrackDetail = sinon.stub()
window.jamClient.JamTrackGetTrackDetail.returns({'key_state' : 'unknown', 'version' : '1'})
window.jamClient.InvalidateJamTrack = sinon.stub()
getJamTrackRightSpy = spyOn(@downloadJamTrack.rest, 'getJamTrackRight')
getJamTrackRightSpy.andCallFake((data) =>
d = $.Deferred();
d.resolve({signing_state: 'QUIET'});
d.promise();
)
spyOn(@downloadJamTrack.rest, 'enqueueJamTrack').andCallFake((data) =>
d = $.Deferred();
d.resolve({});
d.promise();
)
window.jamClient.JamTrackDownload = sinon.stub()
@downloadJamTrack.init()
expect(@downloadJamTrack.attemptedEnqueue).toBe(true)
expect(@downloadJamTrack.transitionTimer?).toBe(true)
getJamTrackRightSpy.reset()
# simulate timer running out, and server check resulting in QUEUED_TIMEOUT
getJamTrackRightSpy.andCallFake((data) =>
d = $.Deferred();
d.resolve({signing_state: 'QUEUED_TIMEOUT'});
d.promise();
)
@downloadJamTrack.transitionCheck()
# the frontend should be saying that it's packaging now
expect(@downloadJamTrack.transitionTimer?).toBe(false)
expect(@showErrorSpy.calledOnce).toBe(true)
expect(@downloadJamTrack.stateHolder.find('.state-errored')).toHaveLength(1)
expect(@downloadJamTrack.stateHolder.find('.state-errored .msg')).toContainText('The server took too long to begin processing your JamTrack.')
expect(@statsSpy.calledOnce).toBe(true)
it "display state correctly", ->
$track = this.syncViewer.createTrack(this.track1)
this.syncViewer.updateTrackState($track)