Squashed commit of the following:

commit 30965c6351
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Sep 15 05:23:27 2015 -0500

    * allow jamblaster to fetch http

commit 5c8fb6b01e
Author: Seth Call <sethcall@gmail.com>
Date:   Fri Sep 11 13:43:07 2015 -0500

    * don't issue stop video in session end

commit 3e27680ea9
Author: Seth Call <sethcall@gmail.com>
Date:   Fri Sep 11 13:40:34 2015 -0500

    * decommision webcam_viewer in session page

commit ac1cc0c828
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Sep 10 07:24:42 2015 -0500

    * VRFS-3541 - don't use HTML to store data sent to server for genre
ID bug in profile

commit 004991119a
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Sep 9 15:10:51 2015 -0500

    * set 'are you our user' cookie to do better job with ad tracking

commit 13a950e65f
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Sep 9 07:58:46 2015 -0500

    * align disable vide obutton better

commit 9722c6cbc6
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Sep 9 07:45:18 2015 -0500

    * whitesapce

commit 3976707b14
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Sep 9 07:13:51 2015 -0500

    * check for video enabled better

commit b483dd537f
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Sep 9 07:02:12 2015 -0500

    * better text for video test

commit a4f465b6d1
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Sep 8 20:30:47 2015 -0500

    * VRFS-3530, VRFS-3531 - allow user to test and disable video

commit ba99f88048
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Sep 8 10:05:26 2015 -0500

    * VRFS-3534 - fix start recording API signature

commit 386ed8144c
Author: Seth Call <sethcall@gmail.com>
Date:   Sun Sep 6 19:03:08 2015 -0500

    * VRFS-3528 - make sure open jamtrack dialog passes
'show_purchased_only'

commit 6d010a561b
Author: Seth Call <sethcall@gmail.com>
Date:   Fri Sep 4 20:43:15 2015 -0500

    * deal with too-few tracks on landing page, and the 3rd CTA bubble
clipping off text

commit 0076f0205a
Author: Seth Call <sethcall@gmail.com>
Date:   Fri Sep 4 15:00:45 2015 -0500

    * VRFS-352 - instrument-centric landing page

commit 3ee71634b3
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Sep 2 09:40:06 2015 -0500

    * remove test stuff

commit d07ac009bf
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Sep 1 08:11:35 2015 -0500

    * VRFS-3509 - case where no device is configured handled

commit 9420cebad4
Author: Seth Call <sethcall@gmail.com>
Date:   Sun Aug 30 05:00:00 2015 -0500

    * VRFS-3494 - show popup when video window launches for the 1st
time to offer guidance

commit c3f81a4d23
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 27 10:35:43 2015 -0500

    * build bump

commit e782d5f9bb
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 27 09:43:40 2015 -0500

    * VRFS-3419 - check better for window opener

commit 36b6699cde
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 27 08:12:47 2015 -0500

    * validate popup VRFS-3419

commit 8948f0498f
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 27 07:59:21 2015 -0500

    * fix changed path

commit 2bce35d604
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 26 20:38:34 2015 -0500

    * fix jamtrack test

commit 63ef63c20d
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 26 20:34:40 2015 -0500

    * fix typo again in webcamViewer. need to go to bed

commit 8566cc5bc9
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 26 20:31:34 2015 -0500

    * fix typo added in webcamViewer

commit 22ea6e89fd
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 26 20:26:39 2015 -0500

    * VRFS-3488 - jamtrack search by artist and song need to pin to the
match, not do a sloppy search

commit a4bd28e168
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 26 16:43:34 2015 -0500

     VRFS-3474 - watch for USB events and refresh video pages

commit d2edfd22c5
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 26 12:01:52 2015 -0500

    * VRFS-3467 - previews are 20 seconds long indicator on jamtracks

commit defdfa8ce9
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 26 06:04:53 2015 -0500

    * VRFS-3473 - fix 'videoShared' state in webcamViewer

commit 090cfa17c0
Merge: 7560b34 818596a
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Aug 25 14:53:35 2015 -0500

    Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into
develop

commit 7560b340c7
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Aug 25 14:52:05 2015 -0500

    * VRFS-3466 - updated frontend to pass in GUIDs

commit 1252dbe178
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Aug 25 05:28:15 2015 -0500

    * use new bridge calls to handle current FPS and resolution
VRFS-3428

commit 818596ae36
Author: Jonathan Kolyer <jonathan@jamkazam.com>
Date:   Tue Aug 25 08:23:52 2015 +0000

    VRFS-3451 musician_search verifying instrument and genres inputs

commit 6918eaf095
Author: Seth Call <sethcall@gmail.com>
Date:   Mon Aug 24 17:55:06 2015 -0500

     more UI tweaks for video settup in account screen VRFS-3428

commit fc69242578
Author: Seth Call <sethcall@gmail.com>
Date:   Mon Aug 24 16:18:31 2015 -0500

    * VRFS-3427 - update FTUE to test video, not just audio

commit 729974013a
Author: Seth Call <sethcall@gmail.com>
Date:   Mon Aug 24 16:17:53 2015 -0500

    * VRFS-3428 - fix button text

commit db1f1d60d5
Merge: 04825d2 90c8d05
Author: Seth Call <sethcall@gmail.com>
Date:   Mon Aug 24 15:56:42 2015 -0500

    Merge branch 'feature/video_frontend' into develop

commit 04825d2659
Author: Seth Call <sethcall@gmail.com>
Date:   Mon Aug 24 15:54:59 2015 -0500

    * VRFS-3428 - update how we query backend for frame rates

commit 39d0731d74
Author: Seth Call <sethcall@gmail.com>
Date:   Sat Aug 22 05:44:59 2015 -0500

    * VRFS-3456 - remove special chars from search

commit 1874720ee8
Author: Seth Call <sethcall@gmail.com>
Date:   Sat Aug 22 05:32:28 2015 -0500

    * VRFS-3456 - protect special chars from tsquery

commit 29104ff09b
Author: Seth Call <sethcall@gmail.com>
Date:   Fri Aug 21 05:02:48 2015 -0500

    * VRFS-3446 - bug fix for no genre specified on join of session;
also fix search bar in jamtrack dialog

commit 3b6d1febdb
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 20 15:44:21 2015 -0500

    * forget cta image

commit 6ac622853c
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 20 15:02:55 2015 -0500

    * VRFS-3449 - a little more tweaking of JamTrack landing page

commit d7fcadcd0d
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 20 14:49:07 2015 -0500

    * VRFS-3450 - fix 'show all tracks' when pagination occurs by not
doubleregistering

commit e7b50ca4a8
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 20 14:19:07 2015 -0500

    * VRFS-3449 - updates for direct landing pages

commit 0d075a9568
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 20 09:19:17 2015 -0500

    * fix spacing issue

commit 9c17d9a024
Merge: 9873450 0b67ef5
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 20 09:06:48 2015 -0500

    Merge branch 'develop' of bitbucket.org:jamkazam/jam-cloud into
develop

commit 98734506df
Author: Seth Call <sethcall@gmail.com>
Date:   Thu Aug 20 09:06:36 2015 -0500

    * VRFS-3448 - fix invisible downloader

commit 90c8d05d00
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 19 14:17:10 2015 -0500

    * wip

commit bf4044d92e
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 19 09:24:14 2015 -0500

    * VRFS-3422 - don't die if the user has on sale_line_items

commit 87c62b4db2
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 19 08:29:22 2015 -0500

    * a fix for linux? hfa code

commit 3fa58715fc
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 19 07:36:04 2015 -0500

    * fix open jamtrack dialog for people with less than 10 jamtracks

commit d045c94f54
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 19 07:17:37 2015 -0500

    * more HFA request polish

commit dc343f10e3
Author: Seth Call <sethcall@gmail.com>
Date:   Wed Aug 19 07:01:47 2015 -0500

    * don't show free jamtrack notice on landing page if
redeemed_jamtrack cookie is set

commit e6618da456
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Aug 18 21:29:15 2015 -0500

    * fix a bug in figuring out if the user should be show GET IT FREE

commit 5ba03a2755
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Aug 18 20:41:37 2015 -0500

    * VRFS-3431 - better response when creating HFA request

commit 37d6c3e57c
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Aug 18 15:19:40 2015 -0500

    * add csv to dump released JamTracks

commit f6101f3621
Author: Seth Call <sethcall@gmail.com>
Date:   Tue Aug 18 14:26:41 2015 -0500

    VRFS-3422, VRFS-3423, VRFS-3424, VRFS-3429 - JamTrack search/listing

commit 0b67ef5f52
Author: Jonathan Kolyer <jonathan@jamkazam.com>
Date:   Sat Aug 15 15:03:00 2015 +0000

    fixed test for instruments in musician search
This commit is contained in:
Steven Miers 2015-09-19 16:33:39 -05:00
parent 900400f053
commit 2e4dfaa728
175 changed files with 41984 additions and 1046 deletions

7
admin/app/admin/csv.rb Normal file
View File

@ -0,0 +1,7 @@
ActiveAdmin.register_page "CSVs" do
menu :parent => 'Misc'
content do
link_to('Released JamTracks', released_jamtracks_csv_path)
end
end

View File

@ -113,6 +113,21 @@ ActiveAdmin.register JamRuby::User, :as => 'Users' do
@user.delete_mod(User::MOD_GEAR, User::MOD_GEAR_FRAME_OPTIONS)
end
if params[:jam_ruby_user][:how_to_use_video_no_show].to_i == 1
@user.mod_merge({User::MOD_NO_SHOW => {User::HOWTO_USE_VIDEO_NOSHOW => true}})
else
@user.delete_mod(User::MOD_NO_SHOW, User::HOWTO_USE_VIDEO_NOSHOW)
end
if params[:jam_ruby_user][:configure_video_no_show].to_i == 1
@user.mod_merge({User::MOD_NO_SHOW => {User::CONFIGURE_VIDEO_NOSHOW=> true}})
else
@user.delete_mod(User::MOD_NO_SHOW, User::CONFIGURE_VIDEO_NOSHOW)
end
@user.save!
redirect_to edit_admin_user_path(@user)

View File

@ -0,0 +1,22 @@
ActiveAdmin.register_page "Harry Fox Request" do
menu :parent => 'JamTracks'
page_action :create_request, :method => :post do
name = params[:jam_ruby_jam_track_hfa_request][:name]
request = JamTrackHfaRequest.create(name)
redirect_to admin_harry_fox_request_path, :notice => "Request created. Check Amazon S3 in the 'jamkazam' bucket; specifically #{request.request_csv_filename}"
end
content do
semantic_form_for JamTrackHfaRequest.new, :url => admin_harry_fox_request_create_request_path, :builder => ActiveAdmin::FormBuilder do |f|
f.inputs "New Harry Fox Licensing Request" do
f.input :name, :hint => "Some sort of name to help us remember what this request was for"
end
f.actions
end
end
end

View File

@ -16,5 +16,4 @@ class EmailController < ApplicationController
@users = User.where(subscribe_email: true)
end
end

View File

@ -0,0 +1,15 @@
require 'csv'
class JamTrackController < ApplicationController
respond_to :html
def dump_released
headers['Content-Disposition'] = "attachment; filename=\"released-jam-tracks.csv\""
headers['Content-Type'] ||= 'text/csv'
@jam_tracks = JamTrack.where(status: 'Production')
render "jam_track/dump_released", :layout => nil
end
end

View File

@ -9,4 +9,7 @@
= f.input :musician
= f.inputs "Gear Mods" do
= f.input :show_frame_options, as: :boolean
= f.inputs "Do Not Shows" do
= f.input :how_to_use_video_no_show, as: :boolean
= f.input :configure_video_no_show, as: :boolean
= f.actions

View File

@ -0,0 +1,6 @@
<%- headers = ['Artist Name', 'Song Name', 'Direct Landing', 'Generic Direct Landing', 'Band Landing'] -%>
<%= CSV.generate_line headers %><%- @jam_tracks.each do |jam_track| -%><%= CSV.generate_line([jam_track.original_artist, jam_track.name,
"https://www.jamkazam.com/landing/jamtracks/#{jam_track.slug}",
"https://www.jamkazam.com/landing/jamtracks/#{jam_track.slug}?generic=1",
"https://www.jamkazam.com/landing/jamtracks/band/#{jam_track.slug}"
]) %><%- end -%>

View File

@ -27,4 +27,13 @@
def show_frame_options
self.get_gear_mod(MOD_GEAR_FRAME_OPTIONS)
end
def how_to_use_video_no_show
self.get_no_show_mod(HOWTO_USE_VIDEO_NOSHOW)
end
def configure_video_no_show
self.get_no_show_mod(CONFIGURE_VIDEO_NOSHOW)
end
end

View File

@ -8,9 +8,6 @@ JamAdmin::Application.routes.draw do
devise_for :users, :class_name => "JamRuby::User", :path_prefix => '/admin', :path => '', :path_names => {:sign_in => 'login', :sign_out => 'logout'}
scope ENV['RAILS_RELATIVE_URL_ROOT'] || '/' do
root :to => "admin/dashboard#index"
@ -28,13 +25,12 @@ JamAdmin::Application.routes.draw do
ActiveAdmin.routes(self)
match '/api/artifacts' => 'artifacts#update_artifacts', :via => :post
match '/api/mix/:id/enqueue' => 'admin/mixes#mix_again', :via => :post
match '/api/checks/latency_tester' => 'checks#check_latency_tester', :via => :get
match '/api/users/emailables/:code' => 'email#dump_emailables', :via => :get
match '/api/jam_tracks/released' => 'jam_track#dump_released', :via => :get, as: 'released_jamtracks_csv'
mount Resque::Server.new, :at => "/resque"

View File

@ -225,6 +225,7 @@ FactoryGirl.define do
factory :jam_track, :class => JamRuby::JamTrack do
sequence(:name) { |n| "jam-track-#{n}" }
sequence(:description) { |n| "description-#{n}" }
sequence(:slug) { |n| "slug-#{n}" }
time_signature '4/4'
status 'Production'
recording_type 'Cover'

View File

@ -303,3 +303,6 @@ jam_track_onboarding_enhancements.sql
jam_track_name_drop_unique.sql
populate_languages.sql
populate_subjects.sql
jam_track_searchability.sql
harry_fox_agency.sql
jam_track_slug.sql

View File

@ -0,0 +1,27 @@
ALTER TABLE jam_tracks ADD COLUMN server_fixation_date DATE DEFAULT NOW();
ALTER TABLE jam_tracks ADD COLUMN hfa_license_status BOOLEAN DEFAULT FALSE;
ALTER TABLE jam_tracks ADD COLUMN hfa_license_desired BOOLEAN DEFAULT TRUE;
ALTER TABLE jam_tracks ADD COLUMN alternative_license_status BOOLEAN DEFAULT FALSE;
ALTER TABLE jam_tracks ADD COLUMN hfa_license_number INTEGER;
ALTER TABLE jam_tracks ADD COLUMN hfa_song_code VARCHAR;
ALTER TABLE jam_tracks ADD COLUMN album_title VARCHAR;
CREATE TABLE jam_track_hfa_requests (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL,
request_csv_filename VARCHAR,
response_csv_filename VARCHAR,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
approved_at TIMESTAMP,
received_at TIMESTAMP
);
CREATE TABLE jam_track_hfa_request_ids (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
jam_track_id VARCHAR(64) NOT NULL REFERENCES jam_tracks(id) ON DELETE SET NULL,
jam_track_hfa_request_id INTEGER REFERENCES jam_track_hfa_requests(id) ON DELETE SET NULL,
request_id INTEGER,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@ -0,0 +1,28 @@
ALTER TABLE jam_tracks ADD COLUMN search_tsv tsvector;
ALTER TABLE jam_tracks ADD COLUMN artist_tsv tsvector;
ALTER TABLE jam_tracks ADD COLUMN name_tsv tsvector;
CREATE FUNCTION jam_tracks_update_tsv() RETURNS TRIGGER AS $$
BEGIN
new.search_tsv = to_tsvector('public.jamenglish', COALESCE(NEW.original_artist, '') || ' ' || COALESCE(NEW.name, '') || ' ' || COALESCE(NEW.additional_info, ''));
new.artist_tsv = to_tsvector('public.jamenglish', COALESCE(NEW.original_artist, ''));
new.name_tsv = to_tsvector('public.jamenglish', COALESCE(NEW.name, ''));
RETURN NEW;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON jam_tracks FOR EACH ROW EXECUTE PROCEDURE
jam_tracks_update_tsv();
CREATE INDEX jam_tracks_search_tsv_index ON jam_tracks USING gin(search_tsv);
CREATE INDEX jam_tracks_artist_tsv_index ON jam_tracks USING gin(artist_tsv);
CREATE INDEX jam_tracks_name_tsv_index ON jam_tracks USING gin(name_tsv);
CREATE INDEX jam_tracks_name_key ON jam_tracks USING btree (name);
CREATE INDEX jam_tracks_original_artist_key ON jam_tracks USING btree (original_artist);
CREATE INDEX jam_tracks_status_key ON jam_tracks USING btree (status);
UPDATE jam_tracks SET original_artist=original_artist, name=name, additional_info=additional_info;

1
db/up/jam_track_slug.sql Normal file
View File

@ -0,0 +1 @@
ALTER TABLE jam_tracks ADD COLUMN slug VARCHAR(2000) UNIQUE;

View File

@ -203,6 +203,8 @@ require "jam_ruby/models/email_batch_scheduled_sessions"
require "jam_ruby/models/email_batch_set"
require "jam_ruby/models/jam_track_licensor"
require "jam_ruby/models/jam_track"
require "jam_ruby/models/jam_track_hfa_request"
require "jam_ruby/models/jam_track_hfa_request_id"
require "jam_ruby/models/jam_track_track"
require "jam_ruby/models/jam_track_right"
require "jam_ruby/models/jam_track_tap_in"

View File

@ -182,6 +182,25 @@ module JamRuby
finish("success", nil)
end
def add_licensor_metadata(vendor, metalocation)
Dir.mktmpdir do |tmp_dir|
@@log.debug("update vendor metadata")
meta_yml = File.join(tmp_dir, 'meta.yml')
if jamkazam_s3_manager.exists?(metalocation)
jamkazam_s3_manager.download(metalocation, meta_yml)
meta = YAML.load_file(meta_yml)
else
meta = {}
end
meta[:licensor] = vendor
File.open(meta_yml, 'w') {|f| f.write meta.to_yaml }
jamkazam_s3_manager.upload(metalocation, meta_yml)
end
end
def is_tency_storage?
assert_storage_set
@storage_format == 'Tency'
@ -421,10 +440,16 @@ module JamRuby
jam_track.sales_region = 'Worldwide'
jam_track.recording_type = 'Cover'
jam_track.description = "This is a JamTrack audio file for use exclusively with the JamKazam service. This JamTrack is a high quality cover of the #{jam_track.original_artist} song \"#{jam_track.name}\"."
jam_track.hfa_license_status = false
jam_track.alternative_license_status = false
jam_track.hfa_license_desired = true
jam_track.server_fixation_date = Time.now
jam_track.slug = metadata['slug'] || jam_track.generate_slug
if is_tency_storage?
jam_track.vendor_id = metadata[:id]
jam_track.licensor = JamTrackLicensor.find_by_name('Tency Music')
#add_licensor_metadata('Tency Music', metalocation)
end
else
if !options[:resync_audio]
@ -1482,6 +1507,14 @@ module JamRuby
end
end
def add_tency_metadata
JamTrackLicensor.find_by_name('Tency Music').jam_tracks.each do |jam_track|
jam_track_importer = JamTrackImporter.new(@storage_format)
jam_track_importer.add_licensor_metadata('Tency Music', jam_track.metalocation)
break
end
end
def create_masters
iterate_song_storage do |metadata, metalocation|
next if metadata.nil?
@ -1711,11 +1744,20 @@ module JamRuby
def remove_s3_special_chars(filename)
filename.tr('/&@:,$=+?;\^`><{}[]#%~|', '')
end
def generate_slugs
JamTrack.all.each do |jam_track|
jam_track.generate_slug
jam_track.save!
end
end
def onboarding_exceptions
JamTrack.all.each do |jam_track|
jam_track.onboarding_exceptions
end
end
def synchronize_all(options)
importers = []
@ -1732,7 +1774,7 @@ module JamRuby
end
if count > 500
break
#break
end
end

View File

@ -7,7 +7,9 @@ module JamRuby
end
module ClassMethods
def s3_manager(options={:bucket => nil, :public => false})
@s3_manager ||= S3Manager.new(options[:bucket] ? options[:bucket] : (options[:public] ? APP_CONFIG.aws_bucket_public : APP_CONFIG.aws_bucket), APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key)
end
end
def s3_manager(options={:bucket => nil, :public => false})

View File

@ -102,27 +102,39 @@ module JamRuby
def self.search_target_class
end
# FIXME: SQL INJECTION
def _genres(rel, query_data=json)
gids = query_data[KEY_GENRES]
unless gids.blank?
allgids = Genre.order(:id).pluck(:id)
gids = gids.select { |gg| allgids.index(gg).present? }
unless gids.blank?
gidsql = gids.join("','")
gpsql = "SELECT player_id FROM genre_players WHERE (player_type = '#{self.class.search_target_class.name}' AND genre_id IN ('#{gidsql}'))"
rel = rel.where("#{self.class.search_target_class.table_name}.id IN (#{gpsql})")
end
end
rel
end
# FIXME: SQL INJECTION
def _instruments(rel, query_data=json)
unless (instruments = query_data[KEY_INSTRUMENTS]).blank?
instrids = Instrument.order(:id).pluck(:id)
instruments = instruments.select { |ii| instrids.index(ii['instrument_id']).present? }
unless instruments.blank?
instsql = "SELECT player_id FROM musicians_instruments WHERE (("
instsql += instruments.collect do |inst|
"instrument_id = '#{inst['instrument_id']}' AND proficiency_level = #{inst['proficiency_level']}"
unless MusicianInstrument::PROFICIENCY_RANGE === (proflvl=inst['proficiency_level'].to_i)
proflvl = MusicianInstrument::LEVEL_INTERMEDIATE
end
"instrument_id = '#{inst['instrument_id']}' AND proficiency_level = #{proflvl}"
end.join(") OR (")
instsql += "))"
rel = rel.where("#{self.class.search_target_class.table_name}.id IN (#{instsql})")
end
end
rel
end

View File

@ -17,7 +17,8 @@ module JamRuby
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genres_jam_tracks_attributes, :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, :genre_ids, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration, as: :admin
:jam_track_tap_ins_attributes, :genre_ids, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration,
:server_fixation_date, :hfa_license_status, :hfa_license_desired, :alternative_license_status, :hfa_license_number, :hfa_song_code, :album_title, as: :admin
validates :name, presence: true, length: {maximum: 200}
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
@ -38,6 +39,13 @@ module JamRuby
validates :reproduction_royalty, inclusion: {in: [nil, true, false]}
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
validates :duration, numericality: {only_integer: true}, :allow_nil => true
validates :hfa_license_status, inclusion: {in: [true, false]}
validates :hfa_license_desired, inclusion: {in: [true, false]}
validates :alternative_license_status, inclusion: {in: [true, false]}
validates :hfa_license_number, numericality: {only_integer: true}, :allow_nil => true
validates :hfa_song_code, length: {maximum: 200}
validates :album_title, length: {maximum: 200}
validates :slug, uniqueness: true
validates_format_of :reproduction_royalty_amount, with: /^\d+\.*\d{0,4}$/, :allow_blank => true
validates_format_of :licensor_royalty_amount, with: /^\d+\.*\d{0,4}$/, :allow_blank => true
@ -215,6 +223,28 @@ module JamRuby
JamTrack.where("original_artist=?", artist_name).all
end
# special case of index
def autocomplete(options, user)
if options[:match].blank?
return {artists: [], songs: []}
end
options[:show_purchased_only] = options[:show_purchased_only]
options[:limit] = options[:limit] || 5
options[:artist_search] = options[:match]
artists, pager = artist_index(options, user)
options.delete(:artist_search)
options[:song_search] = options[:match]
options[:sort_by] = 'jamtrack'
songs, pager = index(options, user)
{artists: artists, songs:songs}
end
def index(options, user)
if options[:page]
page = options[:page].to_i
@ -252,10 +282,35 @@ module JamRuby
query = query.where("jam_track_rights.user_id = ?", user.id)
end
if options[:search]
tsquery = Search.create_tsquery(options[:search])
if tsquery
query = query.where("(search_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:artist_search]
tsquery = Search.create_tsquery(options[:artist_search])
if tsquery
query = query.where("(artist_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:song_search]
tsquery = Search.create_tsquery(options[:song_search])
if tsquery
query = query.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:artist].present?
query = query.where("original_artist=?", options[:artist])
end
if options[:song].present?
query = query.where("name=?", options[:song])
end
if options[:id].present?
query = query.where("jam_tracks.id=?", options[:id])
end
@ -266,10 +321,16 @@ module JamRuby
query = query.order('jam_tracks.original_artist')
else
query = query.group("jam_tracks.id")
if options[:sort_by] == 'jamtrack'
query = query.order('jam_tracks.name')
else
query = query.order('jam_tracks.original_artist, jam_tracks.name')
end
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
end
query = query.where("jam_tracks.status = ?", 'Production') unless user && user.admin
unless options[:genre].blank?
query = query.joins(:genres)
@ -279,13 +340,14 @@ module JamRuby
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type != 'Master'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
count = query.total_entries
if query.length == 0
[query, nil]
if count == 0
[query, nil, count]
elsif query.length < limit
[query, nil]
[query, nil, count]
else
[query, start + limit]
[query, start + limit, count]
end
end
@ -327,6 +389,19 @@ module JamRuby
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
if options[:show_purchased_only]
query = query.joins(:jam_track_rights)
query = query.where("jam_track_rights.user_id = ?", user.id)
end
if options[:artist_search]
tsquery = Search.create_tsquery(options[:artist_search])
if tsquery
query = query.where("(artist_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
unless options[:genre].blank?
query = query.joins(:genres)
query = query.where('genre_id = ? ', options[:genre])
@ -368,5 +443,15 @@ module JamRuby
plan_code[prefix.length..-1]
end
# http://stackoverflow.com/questions/4308377/ruby-post-title-to-slug
def sluggarize(field)
field.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
end
def generate_slug
self.slug = sluggarize(original_artist) + '-' + sluggarize(name)
puts "Self.slug #{self.slug}"
end
end
end

View File

@ -0,0 +1,103 @@
module JamRuby
class JamTrackHfaRequest < ActiveRecord::Base
include JamRuby::S3ManagerMixin
@@log = Logging.logger[JamTrackHfaRequest]
attr_accessible :name, as: :admin
validates :name, presence: true, length: {maximum: 200}
# look through all jam_track requests, and find the highest one that is associated with an approved harry fox requests
def self.find_max()
max = JamTrackHfaRequestId.select('coalesce(max(request_id), 0) as max').joins('INNER JOIN jam_track_hfa_requests ON jam_track_hfa_requests.id = jam_track_hfa_request_id').where('received_at IS NOT NULL').first()['max']
max.to_i
end
def self.create(name)
request = nil
transaction do
max = find_max()
start = max + 1
request = JamTrackHfaRequest.new
request.name = name
request.save!
request.reload
requests = []
JamTrack.where(hfa_license_status: false).where(hfa_license_desired: true).where(alternative_license_status: false).each do |jam_track|
request_id = JamTrackHfaRequestId.new
request_id.jam_track = jam_track
request_id.jam_track_hfa_request = request
request_id.request_id = start
start += 1
request_id.save
request_id.reload # to get back the request_id attribute
requests << request_id
end
request_name = "JamKazam-#{request.id}-#{request.created_at.to_date.to_s}.csv"
Dir.mktmpdir do |tmp_dir|
out = File.join(tmp_dir, request_name)
# Field 1 - HFA Agreement Code - Hardcode to "SSA".
# Field 2 - Manufacturer Number - Hardcode to "M18303".
# Field 3 - Transaction Date - Populate this field with the date that we generate this tab-delimited file, in the format YYYYMMDD - e.g. "20150813".
# Field 4 - License Request Number - This one is slightly more involved. Basically, according to HFA we need to generate a unique numeric ID for each JamTrack license request (as opposed to each unique JamTrack, as we might need to make more than one request per JamTrack if such requests were to fail in some cases). This unique numeric ID per request should start with the number 1, and increment by 1. So I guess this feature will need to remember which of these IDs are used on each run it makes so that it knows where to start on the next run.
# Field 7 - Total Playing Time - Minutes - We already have a JamTrack field for the duration of the JamTrack in seconds. We should keep that field, and keep using it as is. We need to use that field to populate this Field 7 and the next Field 8. So if the duration of the JamTrack in seconds were 90 seconds, then we should set Field 7 to "1" and Field 8 to "30" to signify a length of 1:30.
# Field 8 - Total Playing Time - Seconds - See note above on Field 7.
# Field 9 - Artist Name - Populate this field from the Artist Name field in the JamTrack record - e.g. "AC/DC".
# Field 10 - Song Title - Populate this field from the Song Name field in the JamTrack record - e.g. "Back In Black".
# Field 21 - Configuration Code - Hardcode to "SP".
# Field 22 - License Type - Hardcode to "G".
# Field 23 - Server Fixation Date - Set this to the approximate date that the JamTrack was uploaded to our servers, and format as YYYYMMDD - e.g. "20150813". I'm suggesting we update each JamTrack record with this date, just so that we have a record of this piece of data we submitted to HFA - even though HFA didn't seem at all clear about how this data is used or why it matters.
# Field 24 - Rate Code - Hardcode to "S".
# Field 37 - User Defined - Populate this field with our internal JamKazam unique JamTrack ID. This field value is supposed to be passed back to us from HFA in the processed output file, and we'll need this to associate the HFA License Number with our internal JamTrack ID.
# Field 38 - Track ID - Let's also populate this field with our internal JamKazam unique JamTrack ID, just like Field 37, just for fun.
CSV.open(out, "wb") do |csv|
requests.each do |request|
line = {}
line['1'] = 'SSA'
line['2'] = 'M18303'
line['3'] = Time.now.to_date.strftime('%Y%m%d')
line['4'] = request.request_id
line['7'] = request.jam_track.duration / 60
line['8'] = request.jam_track.duration % 60
line['9'] = request.jam_track.original_artist
line['10'] = request.jam_track.name
line['21'] = 'SP'
line['22'] = 'G'
line['23'] = request.jam_track.server_fixation_date.strftime('%Y%m%d')
line['24'] = 'S'
line['37'] = request.jam_track.id
line['38'] = request.jam_track.id
entry = []
38.times do |i|
entry << line[(i + 1).to_s]
end
csv << entry
end
end
upload_path = "harry_fox_requests/#{request_name}"
s3_manager.upload(upload_path, out, content_type: 'text/csv')
request.request_csv_filename = upload_path
request.save!
end
request
end
end
end
end

View File

@ -0,0 +1,18 @@
module JamRuby
class JamTrackHfaRequestId < ActiveRecord::Base
include JamRuby::S3ManagerMixin
@@log = Logging.logger[JamTrackHfaRequestId]
attr_accessible :name, as: :admin
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
belongs_to :jam_track_hfa_request, class_name: "JamRuby::JamTrackHfaRequest"
validates :jam_track, presence: true
validates :jam_track_hfa_request, presence:true
end
end

View File

@ -99,9 +99,9 @@ module JamRuby
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
# but the url is short lived enough so that it wouldn't be easily shared
def sign_url(expiration_time = 120, bitrate=48)
def sign_url(expiration_time = 120, bitrate=48, secure=true)
field_name = (bitrate==48) ? "url_48" : "url_44"
s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => true})
s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => secure})
end
def delete_s3_files

View File

@ -13,6 +13,11 @@ module JamRuby
belongs_to :player, polymorphic: true
belongs_to :instrument, :class_name => "JamRuby::Instrument"
LEVEL_BEGIN = 1
LEVEL_INTERMEDIATE = 2
LEVEL_EXPERT = 3
PROFICIENCY_RANGE = (LEVEL_BEGIN..LEVEL_EXPERT)
def description
@description = self.instrument.description
end

View File

@ -151,6 +151,14 @@ module JamRuby
sale.recurly_total_in_cents = 0
sale.recurly_currency = 'USD'
if sale.sale_line_items.count == 0
@@log.info("no sale line items associated with sale")
# we must have ditched some of the sale items. let's just abort this sale
sale.destroy
sale = nil
return sale
end
sale_line_item = sale.sale_line_items[0]
sale_line_item.recurly_tax_in_cents = 0
sale_line_item.recurly_total_in_cents = 0

View File

@ -130,15 +130,18 @@ module JamRuby
args = nil
search_terms.each do |search_term|
# remove ( ) ! : from query terms. parser blows up
search_term.gsub!(/[\(\)!:]/, '')
if args == nil
args = search_term
args = '"' + search_term + '"'
else
args = args + " & " + search_term
args = args + " & " + '"' + search_term + '"'
end
end
args = args + ":*"
args
end
def order_param(params, keys=M_ORDERING_KEYS)
ordering = params[:orderby]
ordering.blank? ? keys[0] : keys.detect { |oo| oo.to_s == ordering }

View File

@ -24,6 +24,8 @@ module JamRuby
MOD_GEAR_FRAME_OPTIONS = "show_frame_options"
MOD_NO_SHOW = "no_show"
HOWTO_USE_VIDEO_NOSHOW = 'how-to-use-video'
CONFIGURE_VIDEO_NOSHOW = 'configure-video'
# MIN/MAX AUDIO LATENCY
MINIMUM_AUDIO_LATENCY = 2

View File

@ -736,6 +736,7 @@ FactoryGirl.define do
factory :jam_track, :class => JamRuby::JamTrack do
sequence(:name) { |n| "jam-track-#{n}" }
sequence(:description) { |n| "description-#{n}" }
sequence(:slug) { |n| "slug-#{n}" }
time_signature '4/4'
status 'Production'
recording_type 'Cover'

View File

@ -0,0 +1,34 @@
require 'spec_helper'
describe JamTrackHfaRequest do
include CarrierWave::Test::Matchers
include UsesTempFiles
let(:jamtrack1) {FactoryGirl.create(:jam_track, duration: 90, server_fixation_date: Time.now.to_date ) }
it "creates request" do
JamTrackHfaRequest.find_max().should eq(0)
jamtrack1.touch
JamTrackHfaRequest.create('request1')
request = JamTrackHfaRequest.first
request.request_csv_filename.should_not be_nil
request.approved_at.should be_nil
request.received_at.should be_nil
request_id = JamTrackHfaRequestId.first
request_id.request_id.should_not be_nil
# as long as the previous request is not approved, we don't move on with the counter
JamTrackHfaRequest.find_max().should eq(0)
request.received_at = Time.now
request.save!
# but once it's approved, we move on
JamTrackHfaRequest.find_max().should eq(1)
end
end

View File

@ -159,8 +159,8 @@ describe JamTrack do
describe "index" do
it "empty query" do
query, pager = JamTrack.index({}, user)
query.size.should == 0
query, pager, count = JamTrack.index({}, user)
count.should == 0
end
it "sorts by name" do
@ -190,24 +190,24 @@ describe JamTrack do
jam_track1.save!
jam_track2.save!
query, pager = JamTrack.index({genre: 'rock'}, user)
query.size.should == 1
query, pager, count = JamTrack.index({genre: 'rock'}, user)
count.should == 1
query[0].should eq(jam_track1)
query, pager = JamTrack.index({genre: 'asian'}, user)
query.size.should == 1
query, pager, count = JamTrack.index({genre: 'asian'}, user)
count.should == 1
query[0].should eq(jam_track2)
query, pager = JamTrack.index({genre: 'african'}, user)
query.size.should == 0
query, pager, count = JamTrack.index({genre: 'african'}, user)
count.should == 0
end
it "supports showing purchased only" do
jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'a')
# no results yet
query, pager = JamTrack.index({show_purchased_only:true}, user)
query.size.should == 0
query, pager, count = JamTrack.index({show_purchased_only:true}, user)
count.should == 0
# but after the user buys it, it is returned
FactoryGirl.create(:jam_track_right, jam_track: jam_track1, user: user)
@ -215,6 +215,25 @@ describe JamTrack do
query.size.should == 1
query[0].should eq(jam_track1)
end
it "full text search" do
jam_track1 = FactoryGirl.create(:jam_track_with_tracks, name: 'Take a Chance On Me', original_artist: 'ABBA')
jam_track2 = FactoryGirl.create(:jam_track_with_tracks, name: 'Nothing Chance', original_artist: 'ABBA')
query, pager = JamTrack.index({search: 'Take'}, user)
query.size.should == 1
query[0].should eq(jam_track1)
query, pager = JamTrack.index({search: 'ABB'}, user)
query.size.should == 2
query, pager = JamTrack.index({search: 'Chance'}, user)
query.size.should == 2
query, pager = JamTrack.index({search: 'Chan'}, user)
query.size.should == 2
end
end
describe "validations" do

View File

@ -172,8 +172,9 @@ describe 'Musician Search Model' do
end
it "gets expected number of users" do
instjson = [{ id: Instrument.first.id, level: 2 },
{ id: Instrument.first(2)[1].id, level: 2 }
instjson = [{ instrument_id: Instrument.first.id, proficiency_level: 2 },
{ instrument_id: Instrument.first(2)[1].id, proficiency_level: 2 },
{ instrument_id: 'foo', proficiency_level: 2 },
]
search.update_json_value(MusicianSearch::KEY_INSTRUMENTS, instjson)
expect(search.do_search.count).to eq(3)

View File

@ -97,6 +97,7 @@ gem 'react-rails', '~> 1.0'
source 'https://rails-assets.org' do
gem 'rails-assets-reflux'
gem 'rails-assets-classnames'
gem 'rails-assets-react-select'
end
#group :development, :production do

View File

@ -1,3 +1,4 @@
Jasmine Javascript Unit Tests
=============================

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -47,7 +47,11 @@
if(gon.global.video_available && gon.global.video_available!="none" ) {
var webcamName;
var webcam = context.jamClient.FTUECurrentSelectedVideoDevice()
var webcam = null;
if (context.jamClient.FTUECurrentSelectedVideoDevice) {
webcam = context.jamClient.FTUECurrentSelectedVideoDevice()
}
if (webcam == null || typeof(webcam) == "undefined" || Object.keys(webcam).length == 0) {
webcamName = "None Configured"
} else {

View File

@ -49,11 +49,13 @@ context.JK.AccountJamTracks = class AccountJamTracks
#context.location="client#/createSession"
jamRow = $(e.target).parents("tr")
@createSession(jamRow.data(), true, jamRow.data('jamTrack'))
return false;
groupSession:(e) =>
#context.location="client#/createSession"
jamRow = $(e.target).parents("tr")
@createSession(jamRow.data(), false, jamRow.data('jamTrack'))
return false;
createSession:(sessionData, solo, jamTrack) =>
tracks = context.JK.TrackHelpers.getUserTracks(context.jamClient)
@ -70,8 +72,8 @@ context.JK.AccountJamTracks = class AccountJamTracks
data.musician_access = !solo
data.fan_access = false
data.fan_chat = false
data.genre = [sessionData.genre]
data.genres = [sessionData.genre]
data.genre = $.map(sessionData.jamTrack.genres, (genre) -> genre.id)
data.genres = $.map(sessionData.jamTrack.genres, (genre)-> genre.id)
# data.genres = context.JK.GenreSelectorHelper.getSelectedGenres('#create-session-genre')
# data.musician_access = if $('#musician-access option:selected').val() == 'true' then true else false
# data.approval_required = if $('input[name=\'musician-access-option\']:checked').val() == 'true' then true else false

View File

@ -121,19 +121,24 @@
// Column 2 - genres
var genres = profileUtils.virtualBandGenreList(userDetail.genres)
$virtualBandGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
var genreIds = profileUtils.getGenreIds(profileUtils.virtualBandGenres(userDetail.genres));
$virtualBandGenreList.data('genres', genreIds).html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
genres = profileUtils.traditionalBandGenreList(userDetail.genres)
$traditionalBandGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
var genreIds = profileUtils.getGenreIds(profileUtils.traditionalBandGenres(userDetail.genres));
$traditionalBandGenreList.data('genres', genreIds).html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
genres = profileUtils.paidSessionGenreList(userDetail.genres)
$paidSessionsGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
var genreIds = profileUtils.getGenreIds(profileUtils.paidSessionGenres(userDetail.genres));
$paidSessionsGenreList.data('genres', genreIds).html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
genres = profileUtils.freeSessionGenreList(userDetail.genres)
$freeSessionsGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
var genreIds = profileUtils.getGenreIds(profileUtils.freeSessionGenres(userDetail.genres));
$freeSessionsGenreList.data('genres', genreIds).html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
genres = profileUtils.cowritingGenreList(userDetail.genres)
$cowritingGenreList.html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
var genreIds = profileUtils.getGenreIds(profileUtils.cowritingGenres(userDetail.genres));
$cowritingGenreList.data('genres', genreIds).html(genres && genres.length > 0 ? genres : NONE_SPECIFIED)
// Column 3 - misc (play commitment, rates, cowriting purpose)
$virtualBandCommitment.val(userDetail.virtual_band_commitment)
@ -165,7 +170,7 @@
}
ui.launchGenreSelectorDialog(type, genres, function(selectedGenres) {
$genreList.html(selectedGenres && selectedGenres.length > 0 ? selectedGenres.join(GENRE_LIST_DELIMITER) : NONE_SPECIFIED)
$genreList.data('genres', selectedGenres).html(selectedGenres && selectedGenres.length > 0 ? selectedGenres.join(GENRE_LIST_DELIMITER) : NONE_SPECIFIED)
})
return false
@ -278,24 +283,24 @@
api.updateUser({
virtual_band: $screen.find('input[name=virtual_band]:checked').val(),
virtual_band_genres: $virtualBandGenreList.html() === NONE_SPECIFIED ? [] : $virtualBandGenreList.html().split(GENRE_LIST_DELIMITER),
virtual_band_genres: $virtualBandGenreList.data('genres'),
virtual_band_commitment: $virtualBandCommitment.val(),
traditional_band: $screen.find('input[name=traditional_band]:checked').val(),
traditional_band_genres: $traditionalBandGenreList.html() === NONE_SPECIFIED ? [] : $traditionalBandGenreList.html().split(GENRE_LIST_DELIMITER),
traditional_band_genres: $traditionalBandGenreList.data('genres'),
traditional_band_commitment: $traditionalBandCommitment.val(),
traditional_band_touring: $traditionalTouringOption.val(),
paid_sessions: $screen.find('input[name=paid_sessions]:checked').val(),
paid_session_genres: $paidSessionsGenreList.html() === NONE_SPECIFIED ? [] : $paidSessionsGenreList.html().split(GENRE_LIST_DELIMITER),
paid_session_genres: $paidSessionsGenreList.data('genres'),
paid_sessions_hourly_rate: profileUtils.normalizeMoneyForSubmit($hourlyRate.val()),
paid_sessions_daily_rate: profileUtils.normalizeMoneyForSubmit($dailyRate.val()),
free_sessions: $screen.find('input[name=free_sessions]:checked').val(),
free_session_genres: $freeSessionsGenreList.html() === NONE_SPECIFIED ? [] : $freeSessionsGenreList.html().split(GENRE_LIST_DELIMITER),
free_session_genres: $freeSessionsGenreList.data('genres'),
cowriting: $screen.find('input[name=cowriting]:checked').val(),
cowriting_genres: $cowritingGenreList.html() === NONE_SPECIFIED ? [] : $cowritingGenreList.html().split(GENRE_LIST_DELIMITER),
cowriting_genres: $cowritingGenreList.data('genres'),
cowriting_purpose: $cowritingPurpose.val()
})
.done(postUpdateProfileSuccess)

View File

@ -24,7 +24,6 @@
var $templateOpenSlots = null;
var $templateAccountPendingRsvp = null;
var $templateAccountSessionDetail = null;
var instrument_logo_map = context.JK.getInstrumentIconMap24();
var invitationDialog = null;
var inviteMusiciansUtil = null;
var friendInput=null;

View File

@ -4,7 +4,7 @@
context.JK = context.JK || {};
context.JK.AccountVideoProfile = function (app) {
var $webcamViewer = new context.JK.WebcamViewer()
var webcamViewerReact = null;
function initialize() {
var screenBindings = {
'beforeShow': beforeShow,
@ -12,15 +12,18 @@
};
app.bindScreen('account/video', screenBindings);
$webcamViewer.init($("#account-video-profile .webcam-container"))
var reactElement = React.createElement(window.WebcamViewer, {isVisible: false, show_header: true, show_disable: true});
var reactDomNode = $("#account-video-profile .webcam-container").get(0)
webcamViewerReact = React.render(reactElement, reactDomNode)
}
function beforeShow() {
$webcamViewer.beforeShow()
webcamViewerReact.beforeShow()
}
function beforeHide() {
$webcamViewer.setVideoOff()
webcamViewerReact.beforeHide()
}
this.beforeShow = beforeShow

View File

@ -131,6 +131,12 @@
// context.JK.CurrentSessionModel.onPlaybackStateChange(type, text);
context.MediaPlaybackActions.playbackStateChange(text);
}
else if(type === ALERT_NAMES.VIDEO_WINDOW_OPENED) {
context.VideoActions.videoWindowOpened()
}
else if(type === ALERT_NAMES.VIDEO_WINDOW_CLOSED) {
context.VideoActions.videoWindowClosed()
}
else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) &&
(ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) {
// squelch these events if not in session

View File

@ -111,7 +111,7 @@
}
})
.fail(function() {
window.location = '/client#/jamtrackBrowse'
window.location = '/client#/jamtrack/search'
window.location.reload();
})
})

View File

@ -29,7 +29,7 @@ class CheckoutUtils
@logger.debug("deleted preserve billing");
unless $.cookie(@cookie_name)?
if $.cookie(@cookie_name)?
@logger.error("after deleting the preserve billing cookie, it still exists!")

View File

@ -39,4 +39,6 @@ context.JK.ClientInit = class ClientInit
nativeClientInit: () =>
@gearUtils.bootstrapDefaultPlaybackProfile();
window.VideoActions.checkPromptConfigureVideo()

View File

@ -110,7 +110,8 @@
}
if(options.type == "yes_no") {
$yesBtn.show().unbind('click').click(function() {
var yesText = options.yes_text || 'YES'
$yesBtn.text(yesText).show().unbind('click').click(function() {
if(options.yes) {
options.yes();
}

View File

@ -28,7 +28,14 @@
checked = 'checked';
}
$genres.append('<input type="checkbox" value="' + val.id + '" ' + checked + ' />' + val.description);
var $input = $('<input type="checkbox" />')
$input.val(val.id)
if(checked == 'checked') {
$input.attr('checked', 'checked')
}
$genres.append($input);
$genres.append(val.description);
$genres.append('</li>');
});
}

View File

@ -50,7 +50,7 @@
$browserJamTrackBtn.click(function() {
app.layout.closeDialog('getting-started')
window.location = '/client#/jamtrackBrowse'
window.location = '/client#/jamtrack/search'
return false;
})

View File

@ -13,9 +13,76 @@
var $templateOpenJamTrackRow = null;
var $downloadedTrackHelp = null;
var $whatAreJamTracks = null;
var $searchBtn = null;
var sampleRate = null;
var sampleRateForFilename = null;
var searchQuery = null;
var cookieName = 'jamtrack_session_search'
// called by react autocomplote component
function search(searchType, searchData) {
window.JamTrackSearchInput = searchData;
searchQuery = {searchType: searchType, searchData: searchData}
$.cookie(cookieName, JSON.stringify(searchQuery))
doSearch(searchQuery);
}
function userSearch(e) {
e.preventDefault();
searchQuery = {searchType: 'user-input', searchData: window.JamTrackSearchInput}
$.cookie(cookieName, JSON.stringify(searchQuery))
doSearch(searchQuery);
}
function doSearch(query) {
emptyList();
resetPagination();
app.user().done(function(user) {
var showSearch = (user.purchased_jamtracks_count > perPage)
var $autocomplete = $dialog.find('[data-react-class="JamTrackAutoComplete"]')
if (showSearch) {
$autocomplete.show()
$searchBtn.show()
// if no query specified, look in a cookie for last query
if (!query) {
query = $.cookie(cookieName)
// and parse that cookie if defined
if (query) {
try {
query = JSON.parse(query)
}
catch (e) {
query = {searchType: 'user-input', searchData: ''}
logger.error("unable to parse search query: " + e)
}
}
}
// if still no query (after checking cookie and what was specified in function, then default to anything
if(!query){
query = {searchType: 'user-input', searchData: ''}
}
}
else {
$autocomplete.hide()
$searchBtn.hide()
}
getPurchasedJamTracks(0)
.done(function (data, textStatus, jqXHR) {
// initialize pagination
var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected, 20)
$paginatorHolder.append($paginator);
});
})
}
function emptyList() {
$tbody.empty();
@ -30,19 +97,13 @@
}
function afterShow() {
$dialog.data('result', null)
emptyList();
resetPagination();
showing = true;
sampleRate = context.jamClient.GetSampleRate()
sampleRateForFilename = sampleRate == 48 ? '48' : '44';
doSearch();
getPurchasedJamTracks(0)
.done(function(data, textStatus, jqXHR) {
// initialize pagination
var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected)
$paginatorHolder.append($paginator);
});
}
function afterHide() {
@ -55,7 +116,21 @@
}
function getPurchasedJamTracks(page) {
return rest.getPurchasedJamTracks({page:page + 1, per_page:10})
var query = {page:page + 1, per_page:10}
if (searchQuery && searchQuery.searchData && searchQuery.searchData.length > 0 && searchQuery.searchType && searchQuery.searchType.length > 0) {
if (searchQuery.searchType == 'user-input') {
query.search = searchQuery.searchData
}
else if(searchQuery.searchType == 'artist-select') {
query.artist_search = searchQuery.searchData
}
else if(searchQuery.searchType == 'song-select') {
query.song_search = searchQuery.searchData
}
}
return rest.getPurchasedJamTracks(query)
.done(function(purchasedJamTracks) {
emptyList();
@ -104,6 +179,8 @@
context.JK.helpBubble($whatAreJamTracks, 'no help yet for this topic', {}, {positions:['bottom'], offsetParent: $dialog})
$whatAreJamTracks.on('click', false) // no help yet
$searchBtn.on('click', userSearch)
}
function initialize(){
@ -121,6 +198,7 @@
$templateOpenJamTrackRow = $('#template-jam-track-row')
$downloadedTrackHelp = $dialog.find('.downloaded-jamtrack-help')
$whatAreJamTracks = $dialog.find('.what-are-jamtracks')
$searchBtn = $dialog.find('.search-btn')
registerStaticEvents();
};
@ -128,6 +206,7 @@
this.initialize = initialize;
this.isShowing = function isShowing() { return showing; }
this.search = search; // called by react
}
return this;

View File

@ -243,6 +243,7 @@ context.JK.DownloadJamTrack = class DownloadJamTrack
@logger.debug "downloadCheck"
retry: () =>
@logger.debug "user initiated retry"
@path = []
@path.push('retry')
this.clear()

View File

@ -74,21 +74,30 @@
function FTUESelectVideoCaptureDevice(device, settings) {
return true;
}
function FTUESetVideoEncodeResolution(resolution) {
}
function testVideoRender() {
}
function FTUEGetVideoCaptureDeviceNames() {
return ["Built-in Webcam HD"]
return {"xy323ss": "Built-in Webcam HD"}
}
function FTUECurrentSelectedVideoDevice() {
//return {}
return {"xy323ss": "Built-in Webcam HD"}
}
function FTUEGetAvailableEncodeVideoResolutions() {
return {
1: "1024x768",
2: "800x600"
1 : "CIF (352X288)",
2 : "VGA (640X480)",
3 : "4CIF (704X576)",
4 : "1/2WHD (640X360)",
5 : "WHD (1280X720)",
6 : "FHD (1920x1080)"
}
}
@ -96,6 +105,30 @@
return {}
}
function FTUESetSendFrameRates(fps) {
}
function FTUEGetSendFrameRates() {
return {20:20, 24:24, 30:30}
}
function GetCurrentVideoResolution() {
return 3;
}
function GetCurrentVideoFrameRate() {
return 30;
}
function FTUESetVideoShareEnable(){
}
function FTUEGetVideoShareEnable() {
return false;
}
function isSessVideoShared() {
return videoShared;
}
@ -336,8 +369,8 @@
function GetOS() { return 100000000; }
function GetOSAsString() {
return "Win32";
//return "MacOSX";
//return "Win32";
return "MacOSX";
}
function LatencyUpdated(map) { dbg('LatencyUpdated:' + JSON.stringify(map)); }
@ -433,10 +466,19 @@
}
function SetVideoNetworkTestScore(numClients) {
}
function GetNetworkTestScore() {
return 8;
}
function GetVideoNetworkTestScore() {
return 8;
}
function SetLatencyTestBlocked(blocked) {
}
@ -1036,6 +1078,8 @@
this.IsMyNetworkWireless = IsMyNetworkWireless;
this.SetNetworkTestScore = SetNetworkTestScore;
this.GetNetworkTestScore = GetNetworkTestScore;
this.SetVideoNetworkTestScore = SetVideoNetworkTestScore;
this.GetVideoNetworkTestScore = GetVideoNetworkTestScore;
this.SetLatencyTestBlocked = SetLatencyTestBlocked;
this.isLatencyTestBlocked = isLatencyTestBlocked;
this.GetLastLatencyTestTimes = GetLastLatencyTestTimes;
@ -1220,14 +1264,19 @@
this.OnDownloadAvailable = OnDownloadAvailable;
// Video functionality:
this.FTUESelectVideoCaptureDevice = FTUESelectVideoCaptureDevice
this.testVideoRender = testVideoRender;
this.FTUESelectVideoCaptureDevice = FTUESelectVideoCaptureDevice;
this.FTUESetVideoEncodeResolution = FTUESetVideoEncodeResolution;
this.FTUEGetVideoCaptureDeviceNames = FTUEGetVideoCaptureDeviceNames;
this.FTUECurrentSelectedVideoDevice = FTUECurrentSelectedVideoDevice;
this.FTUEGetAvailableEncodeVideoResolutions = FTUEGetAvailableEncodeVideoResolutions;
this.FTUEGetVideoCaptureDeviceCapabilities = FTUEGetVideoCaptureDeviceCapabilities;
this.FTUEGetSendFrameRates = FTUEGetSendFrameRates;
this.FTUESetSendFrameRates = FTUESetSendFrameRates;
this.GetCurrentVideoResolution = GetCurrentVideoResolution;
this.GetCurrentVideoFrameRate = GetCurrentVideoFrameRate;
this.FTUESetVideoShareEnable = FTUESetVideoShareEnable;
this.FTUEGetVideoShareEnable = FTUEGetVideoShareEnable;
this.isSessVideoShared = isSessVideoShared;
this.SessStopVideoSharing = SessStopVideoSharing;
this.SessStartVideoSharing = SessStartVideoSharing;

View File

@ -43,6 +43,23 @@
context.JK.dropdown($('select', parentSelector));
}
function render2($select, notSelectedString) {
if(!notSelectedString) {
notSelectedString = 'Any Genre'
}
$select.empty();
$select.append('<option value="">' + notSelectedString + '</option>');
var template = $('#template-genre-option').html();
$.each(_genres, function(index, value) {
// value will be a dictionary entry from _genres:
// { value: xxx, label: yyy }
var genreOptionHtml = context.JK.fillTemplate(template, value);
$select.append(genreOptionHtml);
});
context.JK.dropdown($select);
}
function getSelectedGenres(parentSelector) {
var selectedGenres = [];
var selectedVal = $('select', parentSelector).val();
@ -112,6 +129,10 @@
render: function() {
var _args = arguments;
context.JK.GenreSelectorDeferred.done(function(){render.apply(self, _args)})
},
render2: function() {
var _args = arguments;
context.JK.GenreSelectorDeferred.done(function(){render2.apply(self, _args)})
}
};

View File

@ -116,7 +116,16 @@
SHOW_PREFERENCES : 39, // tell frontend to show preferences dialog
USB_CONNECTED : 40, // tell frontend that a USB device was connected
USB_DISCONNECTED : 41, // tell frontend that a USB device was disconnected
LAST_ALERT : 42
JAM_TRACK_SERVER_ERROR : 42, //error talking with server
BAD_INTERVAL_RATE : 43, //the audio gear is calling back at rate that does not match the expected interval
FIRST_AUDIO_PACKET : 44,// we are receiving audio from peer
NETWORK_PORT_MANGLED : 45, // packet from peer indicates network port is being mangled
NO_GLOBAL_CLOCK_SERVER : 46, //can't reach global clock NTP server
GLOBAL_CLOCK_SYNCED : 47, //clock synced
RECORDING_DONE :48, //the recording writer thread is done
VIDEO_WINDOW_OPENED :49, //video window opened
VIDEO_WINDOW_CLOSED :50,
LAST_ALERT : 51
}
// recreate eThresholdType enum from MixerDialog.h
context.JK.ALERT_TYPES = {
@ -171,7 +180,16 @@
39: {"title": "", "message": ""}, // SHOW_PREFERENCES, //show preferences dialog
40: {"title": "", "message": ""}, // USB_CONNECTED
41: {"title": "", "message": ""}, // USB_DISCONNECTED, // tell frontend that a USB device was disconnected
42: {"title": "", "message": ""} // LAST_ALERT
42: {"title": "", "message": ""}, // JAM_TRACK_SERVER_ERROR
43: {"title": "", "message": ""}, // BAD_INTERVAL_RATE
44: {"title": "", "message": ""}, // FIRST_AUDIO_PACKET
45: {"title": "", "message": ""}, // NETWORK_PORT_MANGLED
46: {"title": "", "message": ""}, // NO_GLOBAL_CLOCK_SERVER
47: {"title": "", "message": ""}, // GLOBAL_CLOCK_SYNCED
48: {"title": "", "message": ""}, // RECORDING_DONE
49: {"title": "", "message": ""}, // VIDEO_WINDOW_OPENED
50: {"title": "", "message": ""}, // VIDEO_WINDOW_CLOSED
51: {"title": "", "message": ""} // LAST_ALERT
};
// add the alert's name to the ALERT_TYPES structure
@ -311,7 +329,9 @@
/** NAMED_MESSAGES means messages that we show to the user (dialogs/banners/whatever), that we have formally named */
context.JK.NAMED_MESSAGES = {
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix'
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix',
HOWTO_USE_VIDEO_NOSHOW : 'how-to-use-video',
CONFIGURE_VIDEO_NOSHOW : 'configure-video'
}
context.JK.ChannelGroupIds = {

View File

@ -112,7 +112,7 @@
helpBubble.jamtrackLandingCta = function($element, $alternativeElement) {
if (!$alternativeElement || $element.visible()) {
context.JK.prodBubble($element, 'jamtrack-landing-cta', {}, bigHelpOptions({positions:['top', 'right'], width:240}))
context.JK.prodBubble($element, 'jamtrack-landing-cta', {}, bigHelpOptions({positions:['top', 'right'], width:260}))
}
else if($alternativeElement) {
context.JK.prodBubble($alternativeElement, 'jamtrack-landing-cta', {}, bigHelpOptions({positions:['right']}))

View File

@ -9,6 +9,7 @@
var logger = context.JK.logger;
var rest = new context.JK.Rest();
var _instruments = []; // will be list of structs: [ {label:xxx, value:yyy}, {...}, ... ]
var _instrumentsSorted = [];
var _rsvp = false;
var _noICheck = false;
if (typeof(_parentSelector)=="undefined") {_parentSelector=null}
@ -35,6 +36,17 @@
label: this.description
});
});
_instrumentsSorted = _instruments.slice().sort(sortAlpha)
}
function sortAlpha(a, b) {
if (a.value == b.value)
return 0;
if (a.value < b.value)
return -1;
else
return 1;
}
function render(parentSelector, userInstruments) {
@ -85,6 +97,23 @@
}
function renderDropdown($select, notSelectedString) {
if(!notSelectedString) {
notSelectedString = 'Any Instrument'
}
$select.empty();
$select.append('<option value="">' + notSelectedString + '</option>');
var template = $('#template-instrument-option-simple').html();
$.each(_instrumentsSorted, function(index, value) {
// value will be a dictionary entry from _genres:
// { value: xxx, label: yyy }
var instrumentOptionHtml = context.JK.fillTemplate(template, value);
$select.append(instrumentOptionHtml);
});
context.JK.dropdown($select);
}
function getSelectedInstruments() {
var selectedInstruments = [];
var $selectedVal = $('input[type="checkbox"]:checked', _parentSelector);
@ -152,6 +181,10 @@
var _args = arguments;
context.JK.InstrumentSelectorDeferred.done(function(){render.apply(self, _args)})
}
this.renderDropdown = function() {
var _args = arguments;
context.JK.InstrumentSelectorDeferred.done(function(){renderDropdown.apply(self, _args)})
}
});
})(window,jQuery);

View File

@ -1642,6 +1642,15 @@
});
}
function autocompleteJamTracks(options) {
return $.ajax({
type: "GET",
url: '/api/jamtracks/autocomplete?' + $.param(options),
dataType: "json",
contentType: 'application/json'
});
}
function getJamTrackArtists(options) {
return $.ajax({
type: "GET",
@ -1715,14 +1724,15 @@
type: "POST",
url: '/api/shopping_carts/add_jamtrack?' + $.param(options),
dataType: "json",
contentType: 'applications/json'
contentType: 'application/json'
});
}
function getShoppingCarts() {
// the need for the time de-duplicator indicates we are doing something wrong on the server
return $.ajax({
type: "GET",
url: '/api/shopping_carts',
url: '/api/shopping_carts?time=' + new Date().getTime(),
dataType: "json",
contentType: 'application/json'
});
@ -2050,6 +2060,7 @@
this.getJamTrack = getJamTrack;
this.getJamTrackWithArtistInfo = getJamTrackWithArtistInfo;
this.getJamTracks = getJamTracks;
this.autocompleteJamTracks = autocompleteJamTracks;
this.getJamTrackArtists = getJamTrackArtists;
this.getPurchasedJamTracks = getPurchasedJamTracks;
this.getPaymentHistory = getPaymentHistory;

View File

@ -30,10 +30,12 @@ context.JK.JamTrackPreview = class JamTrackPreview
@playButton = @root.find('.play-button')
@stopButton = @root.find('.stop-button')
@instrumentIcon = @root.find('.instrument-icon')
@instrumentPart = @root.find('.instrument-part')
@instrumentName = @root.find('.instrument-name')
@part = @root.find('.part')
@loading = @root.find('.loading')
@loadingText = @root.find('.loading-text')
@loadingTextText = @root.find('.loading-text-text')
@playButton.on('click', @play)
@stopButton.on('click', @stop)
@ -42,10 +44,12 @@ context.JK.JamTrackPreview = class JamTrackPreview
instrumentId = null
instrumentDescription = '?'
if @jamTrackTrack.track_type == 'Track'
@loadingTextText.text('20 second preview loading')
if @jamTrackTrack.instrument
instrumentId = @jamTrackTrack.instrument.id
instrumentDescription = @jamTrackTrack.instrument.description
else
@loadingTextText.text('preview loading')
instrumentId = 'other'
instrumentDescription= 'Master Mix'
@ -64,7 +68,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
if @options.master_adds_line_break
part = '"' + @jamTrack.name + '"' + ' by ' + @jamTrack.original_artist
@part.html("#{part}") if part != ''
@part.text("#{part}") if part != ''
@part.addClass('adds-line-break')
else
@ -79,6 +83,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@part.text("(#{part})") if part != ''
@instrumentPart.text(@instrumentName.text() + ' ' + @part.text())
if @jamTrackTrack.preview_mp3_url?
@ -109,9 +114,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@sound.unload()
removeNowPlaying: () =>
context.JK.JamTrackPreview.nowPlaying.splice(this)
if context.JK.JamTrackPreview.nowPlaying.length > 0
@logger.warn("multiple jamtrack previews playing")
context.JamTrackPreviewActions.stoppedPlaying(this)
onHowlerEnd: () =>
@ -122,8 +125,8 @@ context.JK.JamTrackPreview = class JamTrackPreview
onHowlerLoad: () =>
@loaded = true
@loading.fadeOut();
@loadingText.fadeOut(); #addClass('hidden')
@loading.fadeOut(2000);
@loadingText.fadeOut(2000); #addClass('hidden')
play: (e) =>
if e?
@ -153,10 +156,7 @@ context.JK.JamTrackPreview = class JamTrackPreview
@logger.debug("play issued for jam track preview")
@sound.play()
for playingSound in context.JK.JamTrackPreview.nowPlaying
playingSound.issueStop()
context.JK.JamTrackPreview.nowPlaying = []
context.JK.JamTrackPreview.nowPlaying.push(this)
context.JamTrackPreviewActions.startedPlaying(this)
@playButton.addClass('hidden')
@stopButton.removeClass('hidden')
@ -182,7 +182,4 @@ context.JK.JamTrackPreview = class JamTrackPreview
return false
context.JK.JamTrackPreview.nowPlaying = []

View File

@ -448,6 +448,7 @@ context.JK.JamTrackScreen=class JamTrackScreen
this.handleExpanded(jamtrackRecord)
initialize:() =>
screenBindings =
'beforeShow': this.beforeShow
'afterShow': this.afterShow

View File

@ -17,7 +17,8 @@ context.JK.JamTrackLanding = class JamTrackLanding
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrackLanding', screenBindings)
#@app.bindScreen('jamtrackLanding', screenBindings)
@screen = $('#jamtrackLanding')
@noFreeJamTrack = @screen.find('.no-free-jamtrack')
@freeJamTrack = @screen.find('.free-jamtrack')
@ -64,14 +65,14 @@ context.JK.JamTrackLanding = class JamTrackLanding
# client#/jamtrack
for artist in artists
artistLink = "<a href='client?artist=#{encodeURIComponent(artist.original_artist)}#/jamtrackBrowse' class='artist-link' artist='#{artist.original_artist}'>#{artist.original_artist} (#{artist.song_count})</a>"
artistLink = "<a href='client?artist=#{encodeURIComponent(artist.original_artist)}#/jamtrack/search' class='artist-link' artist='#{artist.original_artist}'>#{artist.original_artist} (#{artist.song_count})</a>"
@bandList.append("<li>#{artistLink}</li>")
# We don't want to do a full page load if this is clicked on here:
bindArtistLinks:() =>
that=this
@bandList.on "click", "a.artist-link", (event)->
context.location="client#/jamtrackBrowse"
context.location="client#/jamtrack/search"
if window.history.replaceState # ie9 proofing
window.history.replaceState({}, "", this.href)
event.preventDefault()

View File

@ -577,6 +577,8 @@
$(document).triggerHandler(EVENTS.SCREEN_CHANGED, {previousScreen: previousScreen, newScreen: currentScreen})
context.JamTrackPreviewActions.screenChange()
screenEvent(currentScreen, 'beforeShow', data);
// For now -- it seems we want it open always.

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@
* @param onPageSelected when a new page is selected. receives one argument; the page number.
* the function should return a deferred object (whats returned by $.ajax), and that response has to have a 'total-entries' header set
*/
create:function(totalEntries, perPage, currentPage, onPageSelected) {
create:function(totalEntries, perPage, currentPage, onPageSelected, maxPages) {
if(this.$templatePaginator === null) {
this.$templatePaginator = $('#template-paginator')
@ -100,6 +100,13 @@
var pages = calculatePages(totalEntries, perPage);
if(maxPages) {
if((totalEntries / perPage) > maxPages) {
pages = calculatePages(maxPages * perPage, perPage);
}
}
var options = { pages: pages,
currentPage: currentPage };

View File

@ -97,6 +97,17 @@
return list;
}
profileUtils.getGenreIds = function(genres) {
var list = []
for (var i=0; i < genres.length; i++) {
list.push(genres[i].genre_id);
}
return list;
}
// the server stores money in cents; display it as such
profileUtils.normalizeMoneyForDisplay = function(serverValue) {
if (!serverValue || serverValue==="") {

View File

@ -1,7 +1,10 @@
//= require react-input-autosize
//= require react-select
//= require_directory ./react-components/helpers
//= require_directory ./react-components/actions
//= require ./react-components/stores/AppStore
//= require ./react-components/stores/RecordingStore
//= require ./react-components/stores/VideoStore
//= require ./react-components/stores/SessionStore
//= require ./react-components/stores/MixerStore
//= require ./react-components/stores/JamTrackStore
@ -10,7 +13,9 @@
//= require ./react-components/stores/SessionMyTracksStore
//= require ./react-components/stores/SessionOtherTracksStore
//= require ./react-components/stores/SessionMediaTracksStore
//= require ./react-components/stores/PlatformStore
//= require_directory ./react-components/stores
//= require_directory ./react-components/mixins
//= require_directory ./react-components
//= require_directory ./react-components/landing

View File

@ -0,0 +1,111 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackAutoComplete = React.createClass({
EVENTS: context.JK.EVENTS
rest: context.JK.Rest()
logger: context.JK.logger
render: () ->
window.JamTrackSearchInput = '' unless window.JamTrackSearchInput? # can't pass null to react-select
searchValue = if @state.search == 'SEPARATOR' then '' else window.JamTrackSearchInput
`<Select
placeholder="Search for JamTracks"
name="search-field"
asyncOptions={this.getOptions}
autoload={false}
value={searchValue}
onChange={this.onSelectChange}
onBlur={this.onSelectBlur}
onFocus={this.onSelectFocus}
className="autocompleter"
cacheAsyncResults={false}
filterOption={this.filterOption}
clearable={false}
/>`
getInitialState: () ->
({search: ''})
filterOption:() ->
true
onSelectChange: (val) ->
#@logger.debug("CHANGE #{val}")
return false unless val?
if typeof @props.onSearch is 'string'
searchFunction = eval(@props.onSearch)
else
searchFunction = @props.onSearch
search_type
if val.indexOf('ARTIST=') == 0
search_type = 'artist-select'
artist = val['ARTIST='.length..-1]
searchFunction(search_type, artist)
else if val.indexOf('SONG=') == 0
search_type = 'song-select'
song = val['SONG='.length..-1]
searchFunction(search_type, song)
else
@logger.debug("user selected separator")
# this is to signal to the component that the separator was selected, and it has code in render to negate the selection
setTimeout((() =>
@setState({search:val})
), 1)
return false
onSelectFocus: (e) ->
e.preventDefault()
window.JamTrackSearchInput = ''
@setState({search:''})
onSelectBlur: (e) ->
#@logger.debug("blur time")
#@search()
getOptions: (input, callback) ->
#@logger.debug("getOptions input #{input}", this)
# sigh. ugly global
window.JamTrackSearchInput = input
if !input? || input.length == 0
callback(null, {options: [], complete: false})
return
query = {match:input, limit:5}
if @props.show_purchased_only
query.show_purchased_only = true
@rest.autocompleteJamTracks(query)
.done((autocomplete) =>
options = []
for artist in autocomplete.artists
options.push { value: "ARTIST=#{artist.original_artist}", label: "Artist: #{artist.original_artist}" }
if options.length > 0 && autocomplete.songs.length > 0
options.push { value: 'SEPARATOR', label: "---------------"}
for jamtrack in autocomplete.songs
options.push { value: "SONG=#{jamtrack.name}", label: "Song: #{jamtrack.name}" }
callback(null, {options: options, complete: false})
)
})

View File

@ -0,0 +1,401 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackFilterScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
LIMIT: 20
instrument_logo_map: context.JK.getInstrumentIconMap24()
computeWeight: (jam_track_track, instrument) ->
weight = switch
when jam_track_track.track_type == 'Master' then 0
when jam_track_track.instrument?.id == instrument then 1 + jam_track_track.position
else 10000 + jam_track_track.position
render: () ->
searchText = if @state.first_search then 'SEARCH' else 'SEARCH AGAIN'
uiJamTracks = []
for jamtrack in @state.jamtracks
trackRow = context._.clone(jamtrack)
trackRow.track_cnt = jamtrack.tracks.length
trackRow.tracks = []
# if an instrument is selected by the user, then re-order any jam tracks with a matching instrument to the top
instrument = @instrument.val() if @instrument?
if instrument?
jamtrack.tracks.sort((a, b) =>
aWeight = @computeWeight(a, instrument)
bWeight = @computeWeight(b, instrument)
return aWeight - bWeight
)
for track in jamtrack.tracks
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
trackRow.free_state = if @state.is_free then 'free' else 'non-free'
trackRow.is_free = @state.is_free
uiJamTracks.push trackRow
jamtracks = []
for jamtrack in uiJamTracks
jamtrackPricesClasses = { "jamtrack-price" : true }
jamtrackPricesClasses[jamtrack.free_state] = true
jamtrackPricesClasses = classNames(jamtrackPricesClasses)
tracks = []
for track in jamtrack.tracks
tracks.push `<div className="jamtrack-track hidden" key={track.id} data-jamtrack-track-id={track.id}>
<div className="jamtrack-preview">
<JamTrackPreview jamTrack={jamtrack} jamTrackTrack={track} options={{master_shows_duration: true, color:'gray'}} />
<div className="clearall" />
</div>
</div>`
actionBtn = null
if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
actionBtn = `<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id}>ADD TO CART</a>`
availabilityNotice = null
if jamtrack.sales_region==context.JK.AVAILABILITY_US
availabilityNotice =
`<div className="jamtrack-license">
This JamTrack available only to US customers. &nbsp;&nbsp;&nbsp;&nbsp;
<a className="license-us-why" href="#">why?</a>
</div>`
jamtracks.push `<tr className="jamtrack-record" key={jamtrack.id} data-jamtrack-id={jamtrack.id}>
<td className="jamtrack-detail">
<div className="jamtrack-name">"{jamtrack.name}"</div>
<div className="jamtrack-original-artist">by {jamtrack.original_artist}</div>
<br className="clearall"/>
<div className="clearall detail-label extra hidden song-writer">Songwriters:</div>
<div className="detail-value extra hidden">{jamtrack.songwriter}</div>
<div className="clearall detail-label extra hidden">Publishers:</div>
<div className="detail-value extra hidden">{jamtrack.publisher}</div>
<div className="clearall detail-label extra hidden">Genres:</div>
<div className="detail-value extra hidden">{jamtrack.genres.join(', ')}</div>
<div className="clearall detail-label extra hidden">Version:</div>
<div className="detail-value extra hidden">{jamtrack.recording_type}</div>
</td>
<td className="jamtrack-tracks">
<div className="detail-arrow">
<div className="jamtrack-detail-btn">
show all tracks <a className="details-arrow arrow-down"/>
</div>
</div>
{tracks}
</td>
<td className="jamtrack-action">
<div className="jamtrack-action-container">
<div className="jamtrack-actions">
<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>
{actionBtn}
{availabilityNotice}
</div>
</div>
</td>
</tr>`
if @state.searching
jamtracksHeader = "searching..."
else
jamtracksHeader = "search results: #{@state.count} jamtracks"
jamTracksSection =
`<div>
<h2 className="jamtrack-results-header">{jamtracksHeader} <a className="back-to-jamtracks-home" href="/client#/jamtrack">back to jamtracks home</a></h2>
<table className="generaltable jamtrack-table">
<thead>
<tr>
<th className="jamtrack-detail">JAMTRACK</th>
<th className="jamtrack-tracks">TRACKS INCLUDED / PREVIEW</th>
<th className="jamtrack-shop">SHOP</th>
</tr>
</thead>
<tbody className="jamtrack-content">
{jamtracks}
</tbody>
</table>
<div className="end-of-jamtrack-list end-of-list">No more JamTracks</div>
</div>`
options = {}
`<div className="JamTrackFilterScreen">
<div className="content-body-scroller">
{jamTracksSection}
</div>
</div>`
getInitialState: () ->
{search: '', type: 'user-input', jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, count: 0, is_free: context.JK.currentUserFreeJamTrack}
clearResults:() ->
#@content.empty()
#@noMoreJamtracks.hide()
@setState({currentPage: 0, next: null, jamtracks:[], type: 'user-input', searching:false, is_free: context.JK.currentUserFreeJamTrack})
defaultQuery:(extra) ->
query =
per_page: @LIMIT
page: @state.currentPage + 1
if @state.next
query.since = @state.next
instrument = @instrument.val()
if instrument?
query.instrument = instrument
genre = @genre.val()
if genre?
query.genre = genre
$.extend(query, extra)
userSearch: (e) ->
e.preventDefault()
@search()
setFilterState: (state) ->
if state
@genre.easyDropDown('enable').removeAttr('disabled')
@instrument.easyDropDown('enable').removeAttr('disabled')
else
@genre.easyDropDown('disable').attr('disabled', 'disabled')
@instrument.easyDropDown('disable').attr('disabled', 'disabled')
search: (search_type, input) ->
return if @state.searching
$root = $(@getDOMNode())
# disable scroll watching now that we've started a new search
$root.find('.content-body-scroller').off('scroll')
$root.find('.end-of-jamtrack-list').hide()
# we have to make sure the query starts from page 1, and no 'next' from previous causes a 'since' to show up
query = @defaultQuery({page: 1})
delete query.since
@rest.getJamTracks(query)
.done((response) =>
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
)
.fail(() =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
@setState({currentPage: 0, next: null, jamtracks:[], searching: true, count: 0})
componentDidMount: () ->
$screen = $('#jamtrackFilter')
@genre = $screen.find('#jamtrack_genre')
@instrument = $screen.find('#jamtrack_instrument')
@genre.on 'change', this.userSearch
@instrument.on 'change', this.userSearch
# increase dropdown size
context.JK.dropdown(@genre, {cutOff:15})
context.JK.dropdown(@instrument, {cutOff:15})
componentDidUpdate: ( ) ->
$root = $(this.getDOMNode())
$scroller = $root.find('.content-body-scroller')
@setFilterState(!@state.searching)
for jamTrack in @state.jamtracks
jamtrackElement = $root.find("tbody .jamtrack-record[data-jamtrack-id=\"#{jamTrack.id}\"]")
alreadyRegistered = jamtrackElement.data('registered')
unless alreadyRegistered
jamtrackElement.data('jamTrack', jamTrack)
jamtrackElement.data('expanded', true)
jamtrackElement.data('registered', true)
@handleExpanded(jamtrackElement)
@registerEvents(jamtrackElement)
if @state.next == null
$scroller = $root.find('.content-body-scroller')
# if we less results than asked for, end searching
#$scroller.infinitescroll 'pause'
$scroller.off('scroll')
if @state.currentPage == 1 and @state.jamtracks.length == 0
$root.find('.end-of-jamtrack-list').text('No JamTracks found matching your search').show()
@logger.debug("JamTrackSearch: empty search")
else if @state.currentPage > 0
$noMoreJamtracks = $root.find('.end-of-jamtrack-list').text('No more JamTracks').show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
else
@registerInfiniteScroll($scroller)
registerInfiniteScroll:($scroller) ->
$scroller.off('scroll')
$scroller.on('scroll', () =>
# be sure to not fire off many refreshes when user hits the bottom
return if @refreshing
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
$scroller.append('<div class="infinite-scroll-loader-2">... Loading more JamTracks ...</div>')
@refreshing = true
@logger.debug("refreshing more jamtracks for infinite scroll")
@setState({searching:true})
@rest.getJamTracks(@defaultQuery())
.done((json) =>
@setState({jamtracks: @state.jamtracks.concat(json.jamtracks), next: json.next, currentPage: @state.currentPage + 1, count: json.count})
)
.always(() =>
$scroller.find('.infinite-scroll-loader-2').remove()
@refreshing = false
@setState({searching: false})
)
)
playJamtrack:(e) ->
e.preventDefault()
addToCartJamtrack:(e) ->
e.preventDefault()
$target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
else
context.location = '/client#/shoppingCart'
).fail(() => @app.ajaxError)
licenseUSWhy:(e) ->
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:($parent) ->
$parent.find('.play-button').on 'click', @playJamtrack
$parent.find('.jamtrack-add-cart').on 'click', @addToCartJamtrack
$parent.find('.license-us-why').on 'click', @licenseUSWhy
$parent.find('.jamtrack-detail-btn').on 'click', @toggleExpanded
toggleExpanded:(e) ->
e.preventDefault()
jamtrackRecord = $(e.target).parents('.jamtrack-record')
@handleExpanded(jamtrackRecord)
handleExpanded:(trackElement) ->
jamTrack = trackElement.data('jamTrack')
expanded = trackElement.data('expanded')
expand = !expanded
trackElement.data('expanded', expand)
detailArrow = trackElement.find('.jamtrack-detail-btn')
if expand
trackElement.find('.extra').removeClass('hidden')
detailArrow.html('hide tracks <a class="details-arrow arrow-up"></a>')
for track in jamTrack.tracks
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find('.extra').addClass('hidden')
detailArrow.html('show all tracks <a class="details-arrow arrow-down"></a>')
count = 0
for track in jamTrack.tracks
if count < 6
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find("[data-jamtrack-track-id='#{track.id}']").addClass('hidden')
count++
afterShow: (data) ->
@setFilterFromURL()
beforeShow: () ->
@setState({is_free: context.JK.currentUserFreeJamTrack})
setFilterFromURL:() ->
performSearch = false
if $.QueryString['genre']?
performSearch = true
@genre.easyDropDown('select', $.QueryString['genre'], true)
if $.QueryString['instrument']?
performSearch = true
@instrument.easyDropDown('select', $.QueryString['instrument'], true)
unless performSearch
search = context.JamTrackStore.checkRequestedFilter()
if search?
performSearch = true
@genre.easyDropDown('select', search.genre, true)
@instrument.easyDropDown('select', search.instrument, true)
if performSearch
@search()
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrack/filter")
onAppInit: (@app) ->
window.JamTrackSearchInput = '' # need to be not null; otherwise react-select chokes
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrack/filter', screenBindings)
})

View File

@ -0,0 +1,150 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackLandingScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
getInitialState: () ->
{user: null}
render: () ->
howTo = null
if @state.user?.free_jamtrack
howTo =
`<div className="free-jamtrack">
<span>
For a limited time, get one JamTrack free. Search JamTracks below, add one to your shopping cart, and we'll make it free during the checkout process.
</span>
</div>`
else
howTo = `<div className="no-free-jamtrack">
<span>
To play with your JamTracks, open a JamTrack while in a session in the JamKazam app. Or <a href="/client#/account/jamtracks">visit the JamTracks section of your account.</a>
</span>
</div>`
`<div className="content-body-scroller">
<div className="list-columns">
<div className="browse">
<h2>my jamtracks</h2>
<div className="howto">
{howTo}
</div>
<h2 className="browse-jamtracks">search jamtracks</h2>
<div className="search-area">
<div className="search-help para"> To search by the name of the original artist, band, or song name, enter your search words below:</div>
<div className="search-controls">
<JamTrackAutoComplete onSearch={this.search} /><button onClick={this.searchByString} className="search-by-string-btn button-orange ">SEARCH</button>
</div>
<div className="filter-help para">To search by genre and instrument, make your selections below:</div>
<div className="search-controls">
<select className="genre-list easydropdown" name="genres">
<option value="">Any Genre</option>
</select>
<select className="instrument-list easydropdown" name="insruments">
<option value="">Any Instrument</option>
</select>
<button className="search-by-filter-btn button-orange" onClick={this.searchByFilter}>SEARCH</button>
</div>
</div>
</div>
<div className="about">
<h2>what are jamtracks?</h2>
<div className="what">
<div className="details">
JamTracks are the best way to play along with your favorite music! Unlike traditional backing tracks, JamTracks are professionally mastered, complete multitrack recordings, with fully isolated tracks for each part of the master mix. Used with the free JamKazam app & Internet service, you can:
</div>
<ul>
<li>Solo just the part you want to play in order to hear and learn it</li>
<li>Mute just the part you want to play and play along with the rest</li>
<li>Slow down playback to practice without changing the pitch</li>
<li>Change the song key by raising or lowering pitch in half steps</li>
<li>Make audio recordings and share them via Facebook or URL</li>
<li>Make video recordings and share them via YouTube</li>
<li>And even go online to play with others live &amp; in sync</li>
</ul>
<a className="video-thumbnail" href="https://www.youtube.com/watch?v=askHvcCoNfw" rel="external">
<img className="play" src="/assets/content/icon_youtube_play.png" />
</a>
</div>
</div>
</div>
</div>`
componentDidMount: () ->
$root = $(@getDOMNode())
search: (searchType, searchData) ->
context.JamTrackActions.requestSearch(searchType, searchData)
searchByString: (e) ->
e.preventDefault()
context.JamTrackActions.requestSearch('user-input', window.JamTrackSearchInput)
searchByFilter: (e) ->
e.preventDefault()
$root = $(@getDOMNode())
genre = $root.find('select.genre-list').val()
instrument = $root.find('select.instrument-list').val()
context.JamTrackActions.requestFilter(genre, instrument)
afterShow: (data) ->
if context.JK.currentUserId
@app.user().done(@onUser)
else
@onUser({free_jamtrack: context.JK.currentUserFreeJamTrack})
beforeShow: () ->
@setState({user: null})
onUser:(user) ->
@setState({user: user})
# Get artist names and build links
#@rest.getJamTrackArtists({group_artist: true, per_page:100})
#.done(this.buildArtistLinks)
#.fail(this.handleFailure)
# Bind links to action that will open the jam_tracks list view filtered to given artist_name:
# artist_name
#@bindArtistLinks()
onAppInit: (@app) ->
@rest = context.JK.Rest()
@client = context.jamClient
@logger = context.JK.logger
@screen = null
@noFreeJamTrack = null
@freeJamTrack = null
@bandList = null
@noBandsFound = null
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrack', screenBindings)
@screen = $('#jamtrackLanding')
@noFreeJamTrack = @screen.find('.no-free-jamtrack')
@freeJamTrack = @screen.find('.free-jamtrack')
@bandList = @screen.find('#band_list')
@noBandsFound = @screen.find('#no_bands_found')
$root = $(@getDOMNode())
context.JK.GenreSelectorHelper.render2($root.find('select.genre-list'))
@instrumentSelector = new context.JK.InstrumentSelector(@app)
@instrumentSelector.initialize(false, true)
@instrumentSelector.renderDropdown($root.find('select.instrument-list'))
})

View File

@ -0,0 +1,191 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
@JamTrackPreview = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit")]
EVENTS: context.JK.EVENTS
logger: context.JK.logger
propTypes: { options: React.PropTypes.object }
getDefaultProps: () ->
{ options: {master_shows_duration: false, color: 'gray', add_line_break: false, preload_master: false}}
getInitialState: () ->
{ loaded: false, loading: false, playing: false, no_audio: false }
render: () ->
playButtonClasses = { "play-button": true, disabled: @state.no_audio}
playButtonClasses[@props.options.color] = @props.options.color?
playButtonClasses = classNames(playButtonClasses)
stopButtonClasses = { "stop-button": true, disabled: @state.no_audio }
stopButtonClasses[@props.options.color] = @props.options.color?
stopButtonClasses = classNames(stopButtonClasses)
partClasses = {part: true}
partClasses['adds-line-break'] = true if @props.options.master_adds_line_break
partClasses = classNames(partClasses)
if @state.playing
activeButton = `<a className={stopButtonClasses} onClick={this.stop} />`
else
activeButton = `<a className={playButtonClasses} onClick={this.play} />`
loaders = []
if @props.jamTrackTrack.track_type == 'Track'
loading_text = '20 second preview loading'
else
loading_text = 'preview loading'
if @state.loading
loaders.push `<div key="text" className="loading-text">{loading_text}
<div className="loading spinner-small"></div>
</div>`
`<div className="jam-track-preview" data-track-type={this.props.jamTrackTrack.track_type} data-id={this.props.jamTrackTrack.id}>
<div className="actions">
{activeButton}
</div>
<img className="instrument-icon" data-hoveraction="instrument" data-instrument-id={this.state.instrumentId} src={this.state.instrumentSrc} width="24" height="24" />
<div className="instrument-name">{this.state.instrumentDescription}</div>
<div className="part">{this.state.part}</div>
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
{loaders}
</ReactCSSTransitionGroup>
</div>`
componentWillMount: () ->
instrumentId = null
instrumentDescription = '?'
if @props.jamTrackTrack.track_type == 'Track'
if @props.jamTrackTrack.instrument
instrumentId = @props.jamTrackTrack.instrument.id
instrumentDescription = @props.jamTrackTrack.instrument.description
else
instrumentId = 'other'
instrumentDescription= 'Master Mix'
instrumentSrc = context.JK.getInstrumentIcon24(instrumentId)
part = ''
if @props.jamTrackTrack.track_type == 'Track'
part = "(#{@props.jamTrackTrack.part})" if @props.jamTrackTrack.part? && @props.jamTrackTrack.part != instrumentDescription
else
if @props.options.master_adds_line_break
part = '"' + @props.jamTrack.name + '"' + ' by ' + @props.jamTrack.original_artist
else
if @props.options.master_shows_duration
duration = 'entire song'
if @props.jamTrack.duration
duration = "#{context.JK.prettyPrintSeconds(@props.jamTrack.duration)}"
part = duration
else
part = @props.jamTrack.name + ' by ' + @props.jamTrack.original_artist
part = "(#{part})" unless part?
part = '' unless part?
urls = null
no_audio = null
if @props.jamTrackTrack.preview_mp3_url?
urls = [@props.jamTrackTrack.preview_mp3_url]
if @props.jamTrackTrack.preview_ogg_url?
urls.push(@props.jamTrackTrack.preview_ogg_url)
urls = urls
no_audio = false
else
no_audio = true
@setState({
instrumentId: instrumentId,
instrumentDescription: instrumentDescription,
instrumentSrc: instrumentSrc,
part: part
urls: urls,
no_audio: no_audio})
componentDidMount: () ->
$root = $(@getDOMNode());
if @props.options.preload_master && @props.jamTrackTrack.track_type == 'Master' && !@state.no_audio
@sound = new Howl({
src: @state.urls,
autoplay: false,
loop: false,
volume: 1.0,
preload: true,
onload: @onHowlerLoad
onend: @onHowlerEnd})
componentWillUnmount: () ->
@sound.unload() if @sound?
removeNowPlaying: () ->
context.JamTrackPreviewActions.stoppedPlaying(this)
onHowlerEnd: () ->
@logger.debug("on end")
@removeNowPlaying()
@setState(playing: false)
onHowlerLoad: () ->
@setState(loaded: true, loading: false)
play: (e) ->
if e?
e.stopPropagation()
e.preventDefault()
$root = $(@getDOMNode())
$root.triggerHandler(@EVENTS.PREVIEW_PLAYED)
$playButton = $root.find('.play-button')
if @state.no_audio
context.JK.prodBubble($playButton, 'There is no preview available for this track.', {}, {duration:2000})
else
unless @sound?
@sound = new Howl({
src: @state.urls,
autoplay: false,
loop: false,
volume: 1.0,
preload: true,
onload: @onHowlerLoad
onend: @onHowlerEnd})
@logger.debug("play issued for jam track preview")
@sound.play()
context.JamTrackPreviewActions.startedPlaying(this)
@setState({playing: true, loading: !@state.loaded})
issueStop: () ->
@logger.debug("pause issued for jam track preview")
@sound.pause() if @sound? # stop does not actually stop in windows client
@setState({playing: false})
stop: (e) ->
if e?
e.stopPropagation()
e.preventDefault()
if @state.no_audio
context.JK.helpBubble(@playButton, 'There is no preview available for this track.', {}, {duration:2000})
else
@issueStop()
@removeNowPlaying()
return false
})

View File

@ -0,0 +1,539 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@JamTrackSearchScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
LIMIT: 10
instrument_logo_map: context.JK.getInstrumentIconMap24()
input: null
MAX_ARTIST_SHOW: 3
filterOption:() ->
true
render: () ->
searchText = if @state.first_search then 'SEARCH' else 'SEARCH AGAIN'
uiJamTracks = []
for jamtrack in @state.jamtracks
trackRow = context._.clone(jamtrack)
trackRow.track_cnt = jamtrack.tracks.length
trackRow.tracks = []
# if an instrument is selected by the user, then re-order any jam tracks with a matching instrument to the top
###instrument = @instrument.val()
if instrument?
jamtrack.tracks.sort((a, b) =>
aWeight = @computeWeight(a, instrument)
bWeight = @computeWeight(b, instrument)
return aWeight - bWeight
)
###
for track in jamtrack.tracks
trackRow.tracks.push(track)
if track.track_type=='Master'
track.instrument_desc = "Master"
else
inst = '../assets/content/icon_instrument_default24.png'
if track.instrument?
if track.instrument.id in @instrument_logo_map
inst = @instrument_logo_map[track.instrument.id].asset
track.instrument_desc = track.instrument.description
track.instrument_url = inst
if track.part != ''
track.instrument_desc += ' (' + track.part + ')'
trackRow.free_state = if @state.is_free then 'free' else 'non-free'
trackRow.is_free = @state.is_free
uiJamTracks.push trackRow
artists = []
artistsShown = 0
for artist in @state.artists
if @state.show_all_artists || artistsShown < @MAX_ARTIST_SHOW
artists.push `<div key={artist.original_artist}><a className="show-artist" onClick={this.artistNavSelected} data-artist={artist.original_artist}>{artist.original_artist}</a></div>`
artistsShown += 1
artists.push `<div key="no-results" className="no-results">No matching artists</div>` if artists.length == 0
if !@state.show_all_artists && @state.artists.length > @MAX_ARTIST_SHOW
artists.push `<div key="show-hide-artists"><a onClick={this.showAllArtists} className="show-hide-artists">show all <div className="details-arrow arrow-down" /></a></div>`
else if @state.show_all_artists
artists.push `<div key="show-hide-artists"><a onClick={this.hideExtraArtists} className="show-hide-artists">hide artists <div className="details-arrow arrow-up" /></a></div>`
jamtracks = []
for jamtrack in uiJamTracks
jamtrackPricesClasses = { "jamtrack-price" : true }
jamtrackPricesClasses[jamtrack.free_state] = true
jamtrackPricesClasses = classNames(jamtrackPricesClasses)
tracks = []
for track in jamtrack.tracks
tracks.push `<div className="jamtrack-track hidden" key={track.id} data-jamtrack-track-id={track.id}>
<div className="jamtrack-preview">
<JamTrackPreview jamTrack={jamtrack} jamTrackTrack={track} options={{master_shows_duration: true, color:'gray'}} />
<div className="clearall" />
</div>
</div>`
actionBtn = null
if jamtrack.is_free
actionBtn = `<a className="jamtrack-add-cart button-orange is_free" href="#" data-jamtrack-id={jamtrack.id}> GET IT FREE!</a>`
else if jamtrack.purchased
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="javascript:void(0)">PURCHASED</a>`
else if jamtrack.added_cart
actionBtn = `<a className="jamtrack-add-cart-disabled button-grey button-disabled" href="client#/shoppingCart">ALREADY IN CART</a>`
else
actionBtn = `<a className="jamtrack-add-cart button-orange" href="#" data-jamtrack-id={jamtrack.id}>ADD TO CART</a>`
availabilityNotice = null
if jamtrack.sales_region==context.JK.AVAILABILITY_US
availabilityNotice =
`<div className="jamtrack-license">
This JamTrack available only to US customers. &nbsp;&nbsp;&nbsp;&nbsp;
<a className="license-us-why" href="#">why?</a>
</div>`
jamtracks.push `<tr className="jamtrack-record" key={jamtrack.id} data-jamtrack-id={jamtrack.id}>
<td className="jamtrack-detail">
<div className="jamtrack-name">"{jamtrack.name}"</div>
<div className="jamtrack-original-artist">by {jamtrack.original_artist}</div>
<br className="clearall"/>
<div className="clearall detail-label extra hidden song-writer">Songwriters:</div>
<div className="detail-value extra hidden">{jamtrack.songwriter}</div>
<div className="clearall detail-label extra hidden">Publishers:</div>
<div className="detail-value extra hidden">{jamtrack.publisher}</div>
<div className="clearall detail-label extra hidden">Genres:</div>
<div className="detail-value extra hidden">{jamtrack.genres.join(', ')}</div>
<div className="clearall detail-label extra hidden">Version:</div>
<div className="detail-value extra hidden">{jamtrack.recording_type}</div>
</td>
<td className="jamtrack-tracks">
<div className="detail-arrow">
<div className="jamtrack-detail-btn">
show all tracks <a className="details-arrow arrow-down"/>
</div>
</div>
{tracks}
</td>
<td className="jamtrack-action">
<div className="jamtrack-action-container">
<div className="jamtrack-actions">
<div className={jamtrackPricesClasses}>$ {jamtrack.price}</div>
{actionBtn}
{availabilityNotice}
</div>
</div>
</td>
</tr>`
#jamtracks.push `<div className="no-results">No matching JamTracks</div>` if jamtracks.length == 0
searchClasses = classNames({
"button-orange" : true,
"search-btn" : true,
"disabled" : @state.searching
})
artistSection = null
jamTracksSection = null
if @state.type == 'user-input'
if @state.searching
jamtracksHeader = "searching..."
else
jamtracksHeader = "search results: #{@state.count} jamtracks"
else if @state.type == 'artist-select'
jamtracksHeader = "search results: jamtracks for artist \"#{@state.artist}\""
else if @state.type == 'song-select'
jamtracksHeader = "search results: jamtrack \"#{@state.song}\""
else
throw "unknown search type #{@state.type}"
if !@state.first_search
# only show the artists links if the user typed the results
if @state.type == 'user-input'
artistSection =
`<div>
<h2>search results: artists</h2>
<div className="artist-results">
{artists}
</div>
</div>`
jamTracksSection =
`<div>
<h2 className="jamtrack-results-header">{jamtracksHeader} <a className="back-to-jamtracks-home" href="/client#/jamtrack">back to jamtracks home</a></h2>
<table className="generaltable jamtrack-table">
<thead>
<tr>
<th className="jamtrack-detail">JAMTRACK</th>
<th className="jamtrack-tracks">TRACKS INCLUDED / PREVIEW</th>
<th className="jamtrack-shop">SHOP</th>
</tr>
</thead>
<tbody className="jamtrack-content">
{jamtracks}
</tbody>
</table>
<div className="end-of-jamtrack-list end-of-list">No more JamTracks</div>
</div>`
options = {}
searchValue = if @state.search == 'SEPARATOR' then '' else window.JamTrackSearchInput
`<div className="JamTrackSearchScreen">
<div className="controls">
<JamTrackAutoComplete onSearch={this.search} />
<button className={searchClasses} name="search" onClick={this.userSearch}>{searchText}</button>
</div>
<div className="content-body-scroller">
{artistSection}
{jamTracksSection}
</div>
</div>`
clearResults:() ->
@setState({currentPage: 0, next: null, show_all_artists: false, artists:[], jamtracks:[], type: 'user-input', searching:false, artist: null, song:null, is_free: context.JK.currentUserFreeJamTrack, first_search: true})
getInitialState: () ->
{search: '', type: 'user-input', artists:[], jamtracks:[], show_all_artists: false, currentPage: 0, next: null, searching: false, first_search: true, count: 0, is_free: context.JK.currentUserFreeJamTrack}
onSelectChange: (val) ->
#@logger.debug("CHANGE #{val}")
return false unless val?
search_type
if val.indexOf('ARTIST=') == 0
search_type = 'artist-select'
artist = val['ARTIST='.length..-1]
@search(search_type, artist)
else if val.indexOf('SONG=') == 0
search_type = 'song-select'
song = val['SONG='.length..-1]
@search(search_type, song)
else
@logger.debug("user selected separator")
# this is to signal to the component that the separator was selected, and it has code in render to negate the selection
setTimeout((() =>
@setState({search:val})
), 1)
return false
onSelectBlur: (e) ->
#@logger.debug("blur time")
#@search()
showAllArtists: () ->
@setState({show_all_artists: true})
hideExtraArtists: () ->
@setState({show_all_artists: false})
defaultQuery:(extra) ->
query =
per_page: @LIMIT
page: @state.currentPage + 1
sort_by: 'jamtrack'
if @state.next
query.since = @state.next
$.extend(query, extra)
userSearch: (e) ->
e.preventDefault()
@search('user-input', window.JamTrackSearchInput)
search: (search_type, input) ->
return if @state.searching
return unless input?
window.JamTrackSearchInput = input
$root = $(@getDOMNode())
# disable scroll watching now that we've started a new search
#@logger.debug("disabling infinite scroll")
$root.find('.content-body-scroller').off('scroll')
$root.find('.end-of-jamtrack-list').hide()
artistSearch = {limit:100}
if search_type == 'artist-select'
# the user wants to see just artists matching thes exact name
artistSearch.artist = input
else
# the user wants to see anything sort of matching input
artistSearch.artist_search = input
if input?
@rest.getJamTrackArtists(artistSearch)
.done((response) =>
@setState({artists:response.artists})
# we have to make sure the query starts from page 1, and no 'next' from previous causes a 'since' to show up
query = @defaultQuery({page: 1})
delete query.since
@logger.debug("Search type", search_type)
if search_type == 'artist-select'
query.artist = input # works like exact match
else if search_type == 'song-select'
query.song = input # works as exact match
else
query.search = input # works with tsv
@rest.getJamTracks(query)
.done((response) =>
@setState({jamtracks: response.jamtracks, next: response.next, searching: false, first_search: false, currentPage: 1, count: response.count})
)
.fail(() =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
)
.fail(() =>
@app.notifyServerError jqXHR, 'Search Unavailable'
@setState({searching: false, first_search: false})
)
@setState({currentPage: 0, next: null, artists: [], jamtracks:[], searching: true, artist: input, song: input, type: search_type, search:input, count:0})
getOptions: (input, callback) =>
#@logger.debug("getOptions input #{input}", this)
# sigh. ugly global
window.JamTrackSearchInput = input
if !input? || input.length == 0
callback(null, {options: [], complete: false})
return
@rest.autocompleteJamTracks({match:input, limit:5})
.done((autocomplete) =>
options = []
for artist in autocomplete.artists
options.push { value: "ARTIST=#{artist.original_artist}", label: "Artist: #{artist.original_artist}" }
if options.length > 0 && autocomplete.songs.length > 0
options.push { value: 'SEPARATOR', label: "---------------"}
for jamtrack in autocomplete.songs
options.push { value: "SONG=#{jamtrack.name}", label: "Song: #{jamtrack.name}" }
callback(null, {options: options, complete: false})
)
artistNavSelected: (e) ->
e.preventDefault()
@search('artist-select', $(e.target).attr('data-artist'))
componentDidMount: () ->
#@logger.debug("componentDidMount")
componentDidUpdate: ( ) ->
$root = $(this.getDOMNode())
$scroller = $root.find('.content-body-scroller')
for jamTrack in @state.jamtracks
jamtrackElement = $root.find("tbody .jamtrack-record[data-jamtrack-id=\"#{jamTrack.id}\"]")
alreadyRegistered = jamtrackElement.data('registered')
unless alreadyRegistered
jamtrackElement.data('jamTrack', jamTrack)
jamtrackElement.data('registered', true)
jamtrackElement.data('expanded', true)
@handleExpanded(jamtrackElement)
@registerEvents(jamtrackElement)
if @state.next == null
$scroller = $root.find('.content-body-scroller')
# if we less results than asked for, end searching
#$scroller.infinitescroll 'pause'
#@logger.debug("disabling infinite scroll")
$scroller.off('scroll')
if @state.currentPage == 1 and @state.jamtracks.length == 0
$root.find('.end-of-jamtrack-list').text('No JamTracks found matching your search').show()
@logger.debug("JamTrackSearch: empty search")
else if @state.currentPage > 0
@logger.debug("end of search")
$noMoreJamtracks = $root.find('.end-of-jamtrack-list').text('No more JamTracks').show()
# there are bugs with infinitescroll not removing the 'loading'.
# it's most noticeable at the end of the list, so whack all such entries
else
@registerInfiniteScroll($scroller)
registerInfiniteScroll:($scroller) ->
@logger.debug("registering infinite scroll")
$scroller.off('scroll')
$scroller.on('scroll', () =>
# be sure to not fire off many refreshes when user hits the bottom
return if @refreshing
if $scroller.scrollTop() + $scroller.innerHeight() + 100 >= $scroller[0].scrollHeight
$scroller.append('<div class="infinite-scroll-loader-2">... Loading more JamTracks ...</div>')
@refreshing = true
@setState({searching: true})
@logger.debug("refreshing more jamtracks for infinite scroll")
@rest.getJamTracks(@defaultQuery({search:@state.search}))
.done((json) =>
@setState({jamtracks: @state.jamtracks.concat(json.jamtracks), next: json.next, first_search: false, currentPage: @state.currentPage + 1, count: json.count})
)
.always(() =>
$scroller.find('.infinite-scroll-loader-2').remove()
@refreshing = false
@setState({searching: false})
)
)
playJamtrack:(e) ->
e.preventDefault()
addToCartJamtrack:(e) ->
e.preventDefault()
$target = $(e.target)
params = id: $target.attr('data-jamtrack-id')
isFree = $(e.target).is('.is_free')
@rest.addJamtrackToShoppingCart(params).done((response) =>
if(isFree)
if context.JK.currentUserId?
context.JK.currentUserFreeJamTrack = true # make sure the user sees no more free notices
context.location = '/client#/redeemComplete'
else
# now make a rest call to buy it
context.location = '/client#/redeemSignup'
else
context.location = '/client#/shoppingCart'
).fail(() => @app.ajaxError)
licenseUSWhy:(e) ->
e.preventDefault()
@app.layout.showDialog 'jamtrack-availability-dialog'
registerEvents:($parent) ->
$parent.find('.play-button').on 'click', @playJamtrack
$parent.find('.jamtrack-add-cart').on 'click', @addToCartJamtrack
$parent.find('.license-us-why').on 'click', @licenseUSWhy
$parent.find('.jamtrack-detail-btn').on 'click', @toggleExpanded
toggleExpanded:(e) ->
e.preventDefault()
jamtrackRecord = $(e.target).parents('.jamtrack-record')
@handleExpanded(jamtrackRecord)
handleExpanded:(trackElement) ->
jamTrack = trackElement.data('jamTrack')
expanded = trackElement.data('expanded')
expand = !expanded
trackElement.data('expanded', expand)
detailArrow = trackElement.find('.jamtrack-detail-btn')
if expand
trackElement.find('.extra').removeClass('hidden')
detailArrow.html('hide tracks <a class="details-arrow arrow-up"></a>')
for track in jamTrack.tracks
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find('.extra').addClass('hidden')
detailArrow.html('show all tracks <a class="details-arrow arrow-down"></a>')
count = 0
for track in jamTrack.tracks
if count < 6
trackElement.find("[data-jamtrack-track-id='#{track.id}']").removeClass('hidden')
else
trackElement.find("[data-jamtrack-track-id='#{track.id}']").addClass('hidden')
count++
afterShow: (data) ->
@setFilterFromURL()
setFilterFromURL:() ->
performSearch = false
if $.QueryString['artist']?
performSearch = true
@search('artist-select', $.QueryString['artist'])
else if $.QueryString['song']?
performSearch = true
@search('song-select', $.QueryString['song'])
else if $.QueryString['search']?
performSearch = true
@search('user-input', $.QueryString['search'])
else
# check if someone has requested a search for us as we transition to this screen
search = context.JamTrackStore.checkRequestedSearch()
if search?
performSearch = true
@search(search.searchType, search.searchData)
if performSearch
if window.history.replaceState #ie9 proofing
window.history.replaceState({}, "", "/client#/jamtrack/search")
beforeShow: () ->
@setState({is_free: context.JK.currentUserFreeJamTrack})
if !@state.first_search
@search(@state.type, window.JamTrackSearchInput)
onAppInit: (@app) ->
window.JamTrackSearchInput = '' # need to be not null; otherwise react-select chokes
@EVENTS = context.JK.EVENTS
@rest = context.JK.Rest()
@logger = context.JK.logger
screenBindings =
'beforeShow': @beforeShow
'afterShow': @afterShow
@app.bindScreen('jamtrack/search', screenBindings)
})

View File

@ -7,6 +7,12 @@ mixins = []
# this check ensures we attempt to listen if this component is created in a popup
reactContext = if window.opener? then window.opener else window
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
if window.opener?
try
m = window.opener.MixerStore
catch e
reactContext = window
MixerStore = reactContext.MixerStore
MixerActions = reactContext.MixerActions

View File

@ -0,0 +1,108 @@
context = window
logger = context.JK.logger
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
accessOpener = false
if window.opener?
try
m = window.opener.MixerStore
accessOpener = true
catch e
if accessOpener
VideoActions = window.opener.VideoActions
VideoStore = window.opener.VideoStore
#mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
@PopupConfigureVideoGear = React.createClass({
mixins: mixins
logger: context.JK.logger
render: () ->
`<div className="configure-video-geaor">
<div className="popup-contents">
<div classNmae="video-header">
<h2 className="subcaption">video is not configured</h2>
<div className="subcaption">
If you might like to use video in sessions, please select a webcam to use, and a video resolution and frame rate to capture. Then click the TEST WEBCAM button to verify that you see video from your webcam properly. In sessions, you can choose to turn video on or off any time.
</div>
</div>
<div className="webcam-container">
<WebcamViewer isVisible={true} />
</div>
<div className="important-note">
<h5>
Important Note
</h5>
<div className="contents">
You can update your video configuration any time in your Account settings, or in the menus of the video window while in a session.
</div>
</div>
<div className="clearall" />
</div>
<div className="close-behavior">
<span className="field">
<input type="checkbox" name="dont_show" /><label htmlFor="dont_show">Don't show this again</label>
</span>
<a className="button-orange close-link" onClick={this.close}>CLOSE</a>
</div>
</div>`
close: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.configureVideoPopupClosed($dontShow.is(':checked'))
window.close()
windowUnloaded: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.howToUseVideoPopupClosed($dontShow.is(':checked'))
componentDidMount: () ->
$(window).unload(@windowUnloaded)
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
context.JK.checkbox($dontShow)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
componentDidUpdate: () ->
@resizeWindow()
resizeWindow: () =>
$container = $('#minimal-container')
width = $container.width()
height = $container.height()
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
mysteryTopMargin = 20
# deal with chrome in real browsers
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
# handle native client chrome that the above outer-inner doesn't catch
#if navigator.userAgent.indexOf('JamKazam') > -1
#offset += 25
window.resizeTo(width, height + offset)
})

View File

@ -0,0 +1,105 @@
context = window
logger = context.JK.logger
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
accessOpener = false
if window.opener?
try
m = window.opener.MixerStore
accessOpener = true
catch e
if accessOpener
VideoActions = window.opener.VideoActions
VideoStore = window.opener.VideoStore
#mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
@PopupHowToUseVideo = React.createClass({
render: () ->
`<div className="how-to-use-video">
<div className="popup-contents">
<div className="control-holder">
<a className="control start-video" onClick={this.startVideo}>
<span className="helper" />
<img src="/assets/content/webcam-icon-gray.png" width="20" height="20" />
<span id="recording-status">Start Webcam</span>
</a>
</div>
<div className="important-note">
<h5>
Important Note
</h5>
<div className="contents">
You can start and stop your webcam at any time by navigating to the Webcam menu of the video window and selecting Start/Stop Webcam.
</div>
</div>
</div>
<div className="field">
<input type="checkbox" name="dont_show" /><label htmlFor="dont_show">Don't show this again</label>
</div>
<div className="close-behavior">
<a className="button-orange close-link" onClick={this.close}>CLOSE</a>
</div>
</div>`
close: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.howToUseVideoPopupClosed($dontShow.is(':checked'))
window.close()
startVideo: (e) ->
e.preventDefault
VideoActions.startVideo()
windowUnloaded: () ->
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
VideoActions.howToUseVideoPopupClosed($dontShow.is(':checked'))
componentDidMount: () ->
$(window).unload(@windowUnloaded)
$root = jQuery(this.getDOMNode())
$dontShow = $root.find('input[name="dont_show"]')
context.JK.checkbox($dontShow)
@resizeWindow()
# this is necessary due to whatever the client's rendering behavior is.
setTimeout(@resizeWindow, 300)
componentDidUpdate: () ->
@resizeWindow()
resizeWindow: () =>
$container = $('#minimal-container')
width = $container.width()
height = $container.height()
# there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
mysteryTopMargin = 20
# deal with chrome in real browsers
offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
# handle native client chrome that the above outer-inner doesn't catch
#if navigator.userAgent.indexOf('JamKazam') > -1
#offset += 25
window.resizeTo(width, height + offset)
})

View File

@ -3,7 +3,18 @@ logger = context.JK.logger
mixins = []
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
accessOpener = false
if window.opener?
try
m = window.opener.MixerStore
accessOpener = true
catch e
if accessOpener
SessionActions = window.opener.SessionActions
MediaPlaybackStore = window.opener.MediaPlaybackStore
MixerActions = window.opener.MixerActions

View File

@ -2,8 +2,17 @@ context = window
mixins = []
# this check ensures we attempt to listen if this component is created in a popup
if window.opener
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
accessOpener = false
if window.opener?
try
m = window.opener.MixerStore
accessOpener = true
catch e
if accessOpener
mixins.push(Reflux.listenTo(window.opener.RecordingStore,"onRecordingStateChanged"))
@PopupRecordingStartStop = React.createClass({

View File

@ -203,6 +203,8 @@ ChannelGroupIds = context.JK.ChannelGroupIds
contents = null
mediaTracks = []
mediaTracks.push `<div key="download-jamtrack-holder" className="download-jamtrack-holder"></div>`
if this.state.downloadJamTrack?
closeOptions =
`<div>
@ -210,7 +212,6 @@ ChannelGroupIds = context.JK.ChannelGroupIds
<img src="/assets/content/icon_close.png" width="18" height="20" />
Close JamTrack
</a>
<div className="download-jamtrack-holder"></div>
</div>`
contents = closeOptions

View File

@ -31,8 +31,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
<p className="notice">
You have not set up any inputs for your instrument or vocals.
If you want to hear yourself play through the JamKazam app,
and let the app mix your live playing with JamTracks, or with other musicians in online sessions,
<a href="#" className="open-ftue-no-tracks" onClick={this.goToFtue}>click here now.</a>
and let the app mix your live playing with JamTracks, or with other musicians in online sessions, <a href="#" className="open-ftue-no-tracks" onClick={this.goToFtue}>click here now.</a>
</p>
</div>`

View File

@ -3,5 +3,7 @@ context = window
@JamTrackActions = Reflux.createActions({
open: {}
close: {}
requestSearch: {}
requestFilter: {}
})

View File

@ -0,0 +1,8 @@
context = window
@JamTrackPreviewActions = Reflux.createActions({
startedPlaying: {}
stoppedPlaying: {}
screenChange: {}
})

View File

@ -0,0 +1,18 @@
context = window
@VideoActions = Reflux.createActions({
refresh: {}
stopVideo: {}
startVideo: {}
setVideoEncodeResolution: {}
setSendFrameRate: {}
selectDevice: {}
videoWindowOpened : {}
videoWindowClosed : {}
howToUseVideoPopupClosed: {}
toggleVideo: {}
testVideo: {}
configureVideoPopupClosed: {}
checkPromptConfigureVideo: {}
setVideoEnabled: {}
})

View File

@ -606,7 +606,7 @@ MIX_MODES = context.JK.MIX_MODES;
# sanity check
if mixer && mixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
logger.warn("master: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, mixer)
vuMixer = mixer
muteMixer = mixer
@ -618,7 +618,7 @@ MIX_MODES = context.JK.MIX_MODES;
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]
if !oppositeMixer
logger.error("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer )
logger.warn("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer )
when MIX_MODES.PERSONAL
mixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
@ -632,9 +632,9 @@ MIX_MODES = context.JK.MIX_MODES;
# now grab the PeerAudioInputMusicGroup in master mode to satisfy the 'opposite' mixer
oppositeMixer = @getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
if !oppositeMixer
logger.debug("unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", client_id, track.client_track_id)
logger.debug("personal: unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", client_id, track.client_track_id)
else if oppositeMixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
logger.error("personaol: found remote mixer that was not of groupID: PeerAudioInputMusicGroup", client_id, track.client_track_id, mixer)
#vuMixer = oppositeMixer; # for personal mode, use the PeerAudioInputMusicGroup's VUs

View File

@ -4,17 +4,19 @@ context = window
watchVideo: (e) ->
e.preventDefault()
window.open("/popups/youtube/player?id=askHvcCoNfw", 'What Are JamTracks?', 'scrollbars=yes,toolbar=no,status=no,height=282,width=500')
window.open("/popups/youtube/player?id=askHvcCoNfw", 'What Are JamTracks?', 'scrollbars=yes,toolbar=no,status=no,height=540,width=960')
render: () ->
header = null
if @props.band
if @props.instrument
header = "We Have #{@props.instrument_count} JamTracks With #{@props.instrument} Parts - Play Along With Your Favorites!"
else if @props.band
header = "#{@props.jam_track.original_artist} Backing Tracks - Complete Multitracks"
else if @props.generic?
header = "Backing Tracks + Free Amazing App = Unmatched Experience"
else
header = "#{@props.jam_track.name} Backing Track by #{@props.jam_track.original_artist}"
header = "\"#{@props.jam_track.name}\" Backing Track by #{@props.jam_track.original_artist}"
`<div className="one_by_two">

View File

@ -39,22 +39,48 @@ rest = context.JK.Rest()
{processing: false}
render: () ->
bandBrowseUrl = "/client?artist=#{this.props.jam_track.original_artist}#/jamtrackBrowse"
isFree = context.JK.currentUserFreeJamTrack
if isFree
img =`<img src="/assets/web/button_cta_jamtrack_free.png" />`
else
img =`<img src="/assets/web/buy-jamtrack-cta.png" />`
if @props.instrument?
getFreeText = "Get \"#{this.props.jam_track.name}\" JamTrack Free Now"
instrumentBrowseUrl = "/client?instrument=#{this.props.instrument_id}#/jamtrack/filter"
`<div className="cta-holder instrument-selection">
<div className="checkout">
<a href="/client#/jamtrack/search" onClick={this.redeem} className="cta-free-jamtrack" alt="ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!">
{getFreeText}
</a>
<div className="browse-instrument">
<a href={instrumentBrowseUrl}>Or Browse All {this.props.instrument_count} JamTracks With {this.props.instrument} Parts<br/>And Get Your Favorite Free!</a>
</div>
</div>
</div>`
else
bandBrowseUrl = "/client?artist=#{this.props.jam_track.original_artist}#/jamtrack/search"
`<div className="cta-holder">
<div className="checkout">
<a href="/client#/jamtrackBrowse" onClick={this.redeem} className="cta-free-jamtrack" alt="ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!">
<img src="/assets/web/button_cta_jamtrack_free.png" />
<a href="/client#/jamtrack/search" onClick={this.redeem} className="cta-free-jamtrack" alt="ClICK HERE TO PICK YOUR FIRST JAMTRACK FREE!">
{img}
</a>
<span className="value-indicator">$1.99 value</span>
</div>
<br/>
<div className="browse-band">
<a href={bandBrowseUrl}>or browse all {this.props.band_track_count} AC/DC backing tracks</a>
<a href={bandBrowseUrl}>or browse all {this.props.band_track_count} {this.props.jam_track.original_artist} backing tracks</a>
</div>
<br/>
<div className="browse-all">
<a href="/client#/jamtrackBrowse">or browse all {this.props.all_track_count} backing tracks!</a>
<a href="/client?search=#/jamtrack/search">or browse all {this.props.all_track_count} backing tracks!</a>
</div>
</div>`
})

View File

@ -0,0 +1,40 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
EVENTS = context.JK.EVENTS
JamTrackPreviewActions = @JamTrackPreviewActions
@JamTrackPreviewStore = Reflux.createStore(
{
listenables: JamTrackPreviewActions
logger: context.JK.logger
nowPlaying: []
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (app) ->
@app = app
onStartedPlaying: (preview) ->
for playingSound in @nowPlaying
playingSound.issueStop()
@nowPlaying = []
@nowPlaying.push(preview)
onStoppedPlaying: (preview) ->
@nowPlaying.splice(preview)
if @nowPlaying.length > 0
@logger.warn("multiple jamtrack previews playing")
onScreenChange: () ->
for playingSound in @nowPlaying
playingSound.issueStop()
@nowPlaying = []
}
)

View File

@ -11,6 +11,8 @@ JamTrackActions = @JamTrackActions
{
listenables: JamTrackActions
jamTrack: null
requestedSearch: null
requestedFilter: null
init: ->
# Register with the app store to get @app
@ -30,5 +32,26 @@ JamTrackActions = @JamTrackActions
onClose: () ->
@jamTrack = null
this.trigger(@jamTrack)
onRequestSearch:(searchType, searchData) ->
@requestedSearch = {searchType: searchType, searchData: searchData}
window.location.href = '/client#/jamtrack/search'
# needed by the JamTrackSearchScreen
checkRequestedSearch:() ->
requested = @requestedSearch
@requestedSearch = null
requested
onRequestFilter:(genre, instrument) ->
@requestedFilter = {genre: genre, instrument:instrument}
window.location.href = '/client#/jamtrack/filter'
# needed by the JamTrackSearchScreen
checkRequestedFilter:() ->
requested = @requestedFilter
@requestedFilter = null
requested
}
)

View File

@ -0,0 +1,21 @@
$ = jQuery
context = window
logger = context.JK.logger
@PlatformStore = Reflux.createStore(
{
logger: context.JK.logger
os: null
init: ->
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
@os = context.jamClient.GetOSAsString()
this.trigger({os: @os, isWindows: @isWindows})
isWindows: ->
@os == 'Win32'
}
)

View File

@ -9,6 +9,7 @@ JamTrackActions = @JamTrackActions
SessionActions = @SessionActions
RecordingActions = @RecordingActions
NotificationActions = @NotificationActions
VideoActions = @VideoActions
@SessionStore = Reflux.createStore(
{
@ -42,6 +43,7 @@ NotificationActions = @NotificationActions
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
this.listenTo(context.RecordingStore, this.onRecordingChanged)
this.listenTo(context.VideoStore, this.onVideoChanged)
onAppInit: (@app) ->
@ -51,10 +53,8 @@ NotificationActions = @NotificationActions
RecordingActions.initModel(@recordingModel)
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack)
if gon.global.video_available && gon.global.video_available!="none" && context.JK.WebcamViewer?
@webcamViewer = new context.JK.WebcamViewer()
@webcamViewer.init($("#create-session-layout"))
@webcamViewer.setVideoOff()
onVideoChanged: (@videoState) ->
issueChange: () ->
@helper = new context.SessionHelper(@app, @currentSession, @participantsEverSeen, @isRecording, @downloadingJamTrack)
@ -181,8 +181,16 @@ NotificationActions = @NotificationActions
@issueChange()
onToggleSessionVideo: () ->
if @videoState?.videoEnabled
logger.debug("toggle session video")
@webcamViewer.toggleWebcam() if @webcamViewer?
VideoActions.toggleVideo()
else
context.JK.Banner.showAlert({
title: "Video Is Disabled",
html: "To re-enable video, you must go your video settings in your account settings and enable video.",
})
onAudioResync: () ->
logger.debug("audio resyncing")
@ -558,15 +566,14 @@ NotificationActions = @NotificationActions
shareDialog.initialize(context.JK.FacebookHelperInstance);
# initialize webcamViewer
if gon.global.video_available && gon.global.video_available != "none"
@webcamViewer.beforeShow()
VideoActions.stopVideo();
# double-check that we are connected to the server via websocket
return unless @ensureConnected()
# just make double sure a previous session state is cleared out
@sessionEnded()
@sessionEnded(true)
# update the session data to be empty
@updateCurrentSession(null)
@ -973,8 +980,7 @@ NotificationActions = @NotificationActions
logger.warn("no location specified in leaveSession action", behavior)
window.location = '/client#/home'
if gon.global.video_available && gon.global.video_available != "none"
@webcamViewer.setVideoOff()
#VideoActions.stopVideo()
@leaveSession()
@ -1019,7 +1025,7 @@ NotificationActions = @NotificationActions
selfOpenedJamTracks: () ->
@currentSession && (@currentSession.jam_track_initiator_id == context.JK.currentUserId)
sessionEnded: () ->
sessionEnded: (onJoin) ->
# cleanup
context.JK.JamServer.unregisterMessageCallback(context.JK.MessageType.SESSION_JOIN, @trackChanges);
@ -1053,7 +1059,7 @@ NotificationActions = @NotificationActions
@controlsLockedForJamTrackRecording = false
@openBackingTrack = null
@downloadingJamTrack = false
@sessionUtils.setAutoOpenJamTrack(null)
@sessionUtils.setAutoOpenJamTrack(null) unless onJoin
JamTrackActions.close()
NotificationActions.sessionEnded()

View File

@ -0,0 +1,253 @@
$ = jQuery
context = window
logger = context.JK.logger
EVENTS = context.JK.EVENTS
NAMED_MESSAGES = context.JK.NAMED_MESSAGES
VideoActions = @VideoActions
BackendToFrontend = {
1 : "CIF (352x288)",
2 : "VGA (640x480)",
3 : "4CIF (704x576)",
4 : "1/2 720p HD (640x360)",
5 : "720p HD (1280x720)",
6 : "1080p HD (1920x1080)"
}
BackendToFrontendFPS = {
0: 30,
1: 24,
2: 20,
3: 15,
4: 10
}
@VideoStore = Reflux.createStore(
{
listenables: VideoActions
logger: context.JK.logger
videoShared: false
videoOpen : false
state : null
everDisabled : false
init: ->
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
# someone has requested us to refresh our config
onRefresh: ->
# don't do any check if this is a client with no video enabled
return unless context.jamClient.FTUECurrentSelectedVideoDevice?
videoEnabled = context.jamClient.FTUEGetVideoShareEnable()
@videoEnabled = videoEnabled
if videoEnabled
currentDevice = context.jamClient.FTUECurrentSelectedVideoDevice()
deviceNames = context.jamClient.FTUEGetVideoCaptureDeviceNames()
#deviceCaps = context.jamClient.FTUEGetVideoCaptureDeviceCapabilities()
currentResolution = context.jamClient.GetCurrentVideoResolution()
currentFrameRate = context.jamClient.GetCurrentVideoFrameRate()
encodeResolutions = context.jamClient.FTUEGetAvailableEncodeVideoResolutions()
frameRates = context.jamClient.FTUEGetSendFrameRates()
autoSelect = false
if currentResolution == 0
@logger.warn("current resolution not specified; defaulting to VGA")
autoSelect = true
currentResolution = 2
if currentFrameRate == 0
autoSelect = true
@logger.warn("current frame rate not specified; defaulting to 30")
currentFrameRate = 30
else
# backend accepts 10,20,30 etc for FPS, but returns an indexed value (1, 2, 3).
convertedFrameRate = BackendToFrontendFPS[currentFrameRate]
@logger.debug("translating FPS: backend numeric #{currentFrameRate} to #{convertedFrameRate}")
currentFrameRate = convertedFrameRate
# backend needs to be same as frontend
if autoSelect
context.jamClient.FTUESetVideoEncodeResolution(currentResolution)
context.jamClient.FTUESetSendFrameRates(currentFrameRate)
else
@everDisabled = true
# don't talk to the backend when video is disabled; avoiding crashes
currentDevice = null
deviceNames = {}
currentResolution: 0
currentFrameRate: 0
encodeResolutions: {}
frameRates: {}
#deviceCaps: deviceCaps,
@state = {
currentDevice: currentDevice,
deviceNames: deviceNames,
currentResolution: currentResolution,
currentFrameRate: currentFrameRate,
encodeResolutions: encodeResolutions,
frameRates: frameRates,
videoShared: @videoShared
videoOpen: @videoOpen,
videoEnabled: videoEnabled,
everDisabled: @everDisabled
}
this.trigger(@state)
onSetVideoEnabled: (enable) ->
return unless context.jamClient.FTUESetVideoShareEnable?
context.jamClient.FTUESetVideoShareEnable(enable)
# keep state in sync
@state.videoEnabled = enable
@onRefresh()
onStartVideo: ->
return unless context.jamClient.SessStartVideoSharing?
if @howtoWindow?
@howtoWindow.close()
@howtoWindow = null
#else # TESTING
# @howtoWindow = window.open("/popups/how-to-use-video", 'How to Use Video', 'scrollbars=yes,toolbar=no,status=no,height=315,width=320')
@logger.debug("SessStartVideoSharing()")
context.jamClient.SessStartVideoSharing(0)
@videoShared = true
@state.videoShared = @videoShared
this.trigger(@state)
onStopVideo: ->
if @videoShared
@logger.debug("SessStopVideoSharing()")
context.jamClient.SessStopVideoSharing()
@videoShared = false
@state.videoShared = @videoShared
this.trigger(@state)
onTestVideo: () ->
return unless context.jamClient.testVideoRender?
result = context.jamClient.testVideoRender()
if !result
@app.layout.notify({title: 'Unable to initialize video window', text: "Please contact support@jamkazam.com"})
onToggleVideo: () ->
if @videoShared
@onStopVideo()
else
@onStartVideo()
onSetVideoEncodeResolution: (resolution) ->
@logger.debug("set capture resolution: #{resolution}")
context.jamClient.FTUESetVideoEncodeResolution(resolution)
@state.currentResolution = resolution
this.trigger(@state)
onSetSendFrameRate: (frameRates) ->
@logger.debug("set capture frame rate: #{frameRates}")
context.jamClient.FTUESetSendFrameRates(frameRates)
@state.currentFrameRate = frameRates
this.trigger(@state)
onSelectDevice: (device, caps) ->
# don't do anything if no video capabilities
return unless context.jamClient.FTUESelectVideoCaptureDevice?
result = context.jamClient.FTUESelectVideoCaptureDevice(device, caps)
if(!result)
@logger.error("onSelectDevice failed with device #{device}")
@app.layout.notify({title: 'Unable to select webcam', text: "Please try reconnecting webcam."})
else
@state.currentDevice = context.jamClient.FTUECurrentSelectedVideoDevice();
this.trigger(@state)
onVideoWindowOpened: () ->
@onRefresh() unless @state?
@logger.debug("in session? #{context.SessionStore.inSession()}, currentDevice? #{@state?.currentDevice?}, videoShared? #{@videoShared}")
if context.SessionStore.inSession() && @state.currentDevice? && Object.keys(@state.currentDevice).length > 0 && !@videoShared
context.JK.ModUtils.shouldShow(NAMED_MESSAGES.HOWTO_USE_VIDEO_NOSHOW).done((shouldShow) =>
@logger.debug("checking if user has 'should show' on video howto: #{shouldShow}")
if shouldShow
@howtoWindow = window.open("/popups/how-to-use-video", 'How to Use Video', 'scrollbars=yes,toolbar=no,status=no,height=315,width=320')
)
#@howtoWindo.ParentRecordingStore = context.RecordingStore
#@howtoWindo.ParentIsRecording = @recordingModel.isRecording()
@videoOpen = true
@state.videoOpen = @videoOpen
this.trigger(@state)
onVideoWindowClosed: () ->
@onRefresh() unless @state?
if @howtoWindow?
@howtoWindow.close()
@howtoWindow = null
@videoOpen = false
@state.videoOpen = @videoOpen
@videoShared = false
@state.videoShared = @videoShared
this.trigger(@state)
onHowToUseVideoPopupClosed: (dontShow) ->
if (dontShow)
@logger.debug("requesting that user no longer see how-to-use-video")
context.JK.ModUtils.updateNoShow(NAMED_MESSAGES.HOWTO_USE_VIDEO_NOSHOW);
logger.debug("how-to-use-video popup closed")
@howtoWindow = null
onConfigureVideoPopupClosed: (dontShow) ->
if (dontShow)
@logger.debug("requesting that user no longer see configure-video")
context.JK.ModUtils.updateNoShow(NAMED_MESSAGES.CONFIGURE_VIDEO_NOSHOW);
logger.debug("configure-video popup closed")
@configureWindow = null
# if the user passes all the safeguards, let's see if we should get them to configure video
onCheckPromptConfigureVideo: () ->
# don't do any check if this is a client with no video enabled
return unless context.jamClient.FTUECurrentSelectedVideoDevice?
@onRefresh() unless @state?
@logger.debug("checkPromptConfigureVideo", @state.currentDevice, @state.deviceNames)
# if no device configured and this is the native client and if you have at least 1 video
# currentDevice, from the backend, is '{'':''}' in the case of no device configured. But we also check for an empty object, or null object.
if (!@state.currentDevice? || Object.keys(@state.currentDevice).length == 0 || (Object.keys(@state.currentDevice).length == 1 && @state.currentDevice[''] == '')) && gon?.isNativeClient && Object.keys(@state.deviceNames).length > 0
# and if they haven't said stop bothering me about this
context.JK.ModUtils.shouldShow(NAMED_MESSAGES.CONFIGURE_VIDEO_NOSHOW).done((shouldShow) =>
@logger.debug("checking if user has 'should show' on video config: #{shouldShow}")
if shouldShow
@configureWindow = window.open("/popups/configure-video", 'Configure Video', 'scrollbars=yes,toolbar=no,status=no,height=395,width=444')
)
isVideoEnabled:() ->
return @videoEnabled
}
)

View File

@ -0,0 +1,405 @@
context = window
logger = context.JK.logger
reactContext = if window.opener? then window.opener else window
# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
if window.opener?
try
m = window.opener.MixerStore
catch e
reactContext = window
VideoStore = reactContext.VideoStore
VideoActions = reactContext.VideoActions
PlatformStore = reactContext.PlatformStore
ALERT_NAMES = context.JK.ALERT_NAMES;
BackendToFrontend = {
1 : "CIF (352x288)",
2 : "VGA (640x480)",
3 : "4CIF (704x576)",
4 : "1/2 720p HD (640x360)",
5 : "720p HD (1280x720)",
6 : "1080p HD (1920x1080)"
}
BackendNumericToBackendString = {
1 : "CIF (352X288)",
2 : "VGA (640X480)",
3 : "4CIF (704X576)",
4 : "1/2WHD (640X360)",
5 : "WHD (1280X720)",
6 : "FHD (1920x1080)"
}
BackendToFrontendFPS = {
1: 30,
2: 24,
3: 20,
4: 15,
5: 10
}
FrontendToBackend = {}
for key, value of BackendToFrontend
FrontendToBackend[value] = key
mixins = []
mixins.push(Reflux.listenTo(VideoStore, 'onVideoStateChanged'))
@WebcamViewer = React.createClass({
mixins: mixins
logger: context.JK.logger
visible: false
getInitialState: () ->
{
currentDevice: null
deviceNames: {}
deviceCaps: null
currentResolution: 0
currentFrameRate: 0
encodeResolutions: {}
frameRates: {}
rescanning: false
}
onVideoStateChanged: (changes) ->
@setState(changes)
render: () ->
if @props.showBackBtn
backBtn = `<a className="hidden button-grey back-btn" onClick={this.back}>BACK</a>`
selectedDevice = this.selectedDeviceName(@state)
# build list of webcams
webcams = []
noneSelected = selectedDevice == null || selectedDevice.length == 0
# the backend does not allow setting no video camera. So if a webcam is selected, prevent un-selecting
if noneSelected
webcams.push `<option key="none" value="" selected={noneSelected}>None Selected</option>`
context._.each @state.deviceNames, (deviceName, deviceGuid) ->
selected = deviceGuid == selectedDevice
webcams.push `<option key={deviceGuid} value={deviceGuid} selected={selected}>{deviceName}</option>`
noWebcams = Object.keys(@state.deviceNames).length == 0
# build list of capture resolutions
captureResolutions = []
# load current settings from backend
currentResolution = @state.currentResolution
currentFrameRate = @state.currentFrameRate
# protect against non-video clients pointed at video-enabled server from getting into a session
resolutions = @state.encodeResolutions
frames = @state.frameRates
context._.each resolutions, (resolution, resolutionKey, obj) =>
#{1: "CIF (352X288)", 2: "VGA (640X480)", 3: "4CIF (704X576)", 4: "1/2WHD (640X360)", 5: "WHD (1280X720)", 6: "FHD (1920x1080)"}
context._.each frames, (frame, key, obj) =>
frontendResolution = BackendToFrontend[resolutionKey]
@logger.error("unknown resolution! #{resolution}", BackendToFrontend) unless frontendResolution
value = "#{resolutionKey}|#{frame}"
text = "#{frontendResolution} at #{frame} fps"
selected = currentResolution + '|' + currentFrameRate == value
captureResolutions.push `<option key={value} value={value} selected={selected}>{text}</option>`
testBtnClassNames = {'button-orange' : true, 'webcam-test-btn' : true}
if noWebcams
if PlatformStore.isWindows()
testBtnClassNames.disabled = !@state.videoEnabled
testBtnClasses = classNames(testBtnClassNames)
testBtn = `<a className={testBtnClasses} onClick={this.toggleWebcam}>TEST VIDEO</a>`
else
testBtn = null
else if @state.videoShared
testBtnClassNames.disabled = !@state.videoEnabled
testBtnClasses = classNames(testBtnClassNames)
testBtn = `<a className={testBtnClasses} onClick={this.toggleWebcam}>STOP WEBCAM</a>`
else
testBtnClassNames.disabled = !@state.videoEnabled || noneSelected
testBtnClasses = classNames(testBtnClassNames)
testBtn = `<a className={testBtnClasses} onClick={this.toggleWebcam}>TEST WEBCAM</a>`
if @state.rescanning
rescanning =
`<span className="rescanning-notice">
<span className="spinner-small" />
CHECKING GEAR
</span>`
if @props.show_header
if noWebcams
if PlatformStore.isWindows()
testVideoHelpText = `<span>The TEST VIDEO button will open the JamKazam video window to verify that receiving video works on your system.</span>`
header = `<div className="video-header">
<h2 className="subcaption">video gear:</h2>
<div className="subcaption">
JamKazam does not detect any webcams. You will not be able to send video, but you can still receive it from others. {testVideoHelpText}
</div>
</div>`
else
header =
`<div className="video-header">
<h2 className="subcaption">video gear:</h2>
<div className="subcaption">
Select webcam to use for video in sessions. Verify that you see video from webcam in the external application window (it may be behind this window).
</div>
</div>`
if @state.videoEnabled
disableVideoBtnText = "DISABLE VIDEO"
else
disableVideoBtnText = "ENABLE VIDEO"
if @props.show_disable || !@state.videoEnabled || @state.everDisabled
if @state.videoEnabled
disableHelpBtn = `<a className="ftue-video-disable-help">[?]</a>`
disableBtnClasses = classNames({'button-grey' : true, 'disable-video' : true, 'disabled' : @state.videoShared})
disableVideo =
`<div className="webcam-select-container wizard_control">
<a className={disableBtnClasses} onClick={this.disableVideo}>{disableVideoBtnText}</a>
{disableHelpBtn}
</div>`
`<div className="webcam-viewer">
{header}
<form className="video">
<h2 className="sub-header select-webcam">select webcam:</h2>
<div className="webcam-select-container wizard_control">
<select onChange={this.selectWebcam} disabled={noWebcams || !this.state.videoEnabled}>
{webcams}
</select>
</div>
<h2 className="sub-header select-resolution">select video capture resolution & frame rate:</h2>
<div className="webcam-resolution-select-container wizard_control">
<select onChange={this.selectResolution} disabled={noWebcams || !this.state.videoEnabled}>
{captureResolutions}
</select>
<a className="ftue-video-settings-help">[?]</a>
</div>
<div className="configure-webcam wizard_control">
{backBtn}
{testBtn}
</div>
{rescanning}
</form>
{disableVideo}
</div>`
componentDidMount: () ->
if @props.isVisible
@beforeShow()
$root = $(@getDOMNode())
$videoSettingsHelp = $root.find('.ftue-video-settings-help')
context.JK.helpBubble($videoSettingsHelp, 'ftue-video-settings', {}, {width:300}) if $videoSettingsHelp.length > 0
$videoSettingsHelp.click(false)
$videoDisableHelp = $root.find('.ftue-video-disable-help')
context.JK.helpBubble($videoDisableHelp, 'ftue-video-disable', {}, {width:300}) if $videoDisableHelp.length > 0
$videoDisableHelp.click(false)
componentWillUpdate: (nextProps, nextState) ->
# protect against non-video clients pointed at video-enabled server from getting into a session
@logger.debug("webcam devices", nextState.deviceNames, @state.deviceNames)
if !@initialScan?
@initialScan = true
else if @visible
@findChangedWebcams(nextState.deviceNames, @state.deviceNames)
componentWillReceiveProps:(nextProps) ->
if nextProps.isVisible
@beforeShow()
else
@beforeHide()
beforeShow:() ->
@visible = true
VideoActions.refresh()
VideoActions.stopVideo()
context.JK.onBackendEvent(ALERT_NAMES.USB_CONNECTED, 'webcam-viewer', @onUsbDeviceConnected);
context.JK.onBackendEvent(ALERT_NAMES.USB_DISCONNECTED, 'webcam-viewer', @onUsbDeviceDisconnected);
beforeHide: () ->
@visible = false
context.JK.offBackendEvent(ALERT_NAMES.USB_CONNECTED, 'webcam-viewer', @onUsbDeviceConnected);
context.JK.offBackendEvent(ALERT_NAMES.USB_DISCONNECTED, 'webcam-viewer', @onUsbDeviceDisconnected);
if @rescanTimeout?
clearTimeout(@rescanTimeout)
@rescanTimeout = null
@setVideoOff()
onUsbDeviceConnected: () ->
# don't handle USB events when minimized
#return if !context.jamClient.IsFrontendVisible()
logger.debug("USB device connected")
@scheduleRescanSystem(3000)
onUsbDeviceDisconnected:() ->
# don't handle USB events when minimized
#return if !context.jamClient.IsFrontendVisible()
logger.debug("USB device disconnected")
@scheduleRescanSystem(3000)
scheduleRescanSystem: (time) ->
if @rescanTimeout?
clearTimeout(@rescanTimeout)
@rescanTimeout = null
@setState({rescanning: true})
@rescanTimeout = setTimeout(() =>
@setState({rescanning: false})
VideoActions.refresh()
, time)
selectWebcam:(e) ->
e.preventDefault()
device = $(e.target).val()
VideoActions.selectDevice(device, {})
disableVideo: (e) ->
e.preventDefault()
return if @state.videoShared
if @state.videoEnabled
context.JK.Banner.showYesNo({
title: "Disable Video?",
html: "You will not be able to send or receive video.",
yes: =>
VideoActions.setVideoEnabled(false)
})
else
VideoActions.setVideoEnabled(true)
updateBackend: (selectedResolution, selectedFps) ->
@logger.debug 'Selecting webcam resolution: ', selectedResolution
@logger.debug 'Selecting webcam fps: ', selectedFps
VideoActions.setVideoEncodeResolution(selectedResolution)
VideoActions.setSendFrameRate(selectedFps)
selectResolution:(e) ->
e.preventDefault()
resolution = $(e.target).val()
@logger.debug 'new capture resolution selected: ' + resolution
if resolution?
bits = resolution.split('|')
selectedResolution = bits[0]
selectedFps = bits[1]
@updateBackend(selectedResolution, selectedFps)
setVideoOff:() ->
VideoActions.stopVideo()
back: () =>
window.location = '/client#/account'
toggleWebcam:(e) ->
e.preventDefault()
return unless this.state.videoEnabled
$toggleBtn = $(e.target)
# we should only do this if no device is currently selected
$root = $(@getDOMNode())
$select = $root.find('.webcam-select-container select')
if Object.keys(@state.deviceNames).length == 0
context.JK.Banner.showYesNo({
yes_text: 'RUN TEST',
title: "Run Video Test?",
html: "A video window will show up with changing colors and shapes for 10 seconds. The test was successful if you were able to see the changing colors. Close the window once the colors and shapes stop changing.",
yes: =>
VideoActions.testVideo()
})
else
device = $select.val()
#VideoActions.selectDevice(device, {})
VideoActions.toggleVideo()
#if this.isVideoShared()
# $toggleBtn.removeClass("selected")
# VideoActions.stopVideo()
# @setState({videoShared: false})
#else
# $toggleBtn.addClass("selected")
# VideoActions.startVideo()
# @setState({videoShared: true})
selectedDeviceName:(state) ->
webcamName = null
# protect against non-video clients pointed at video-enabled server from getting into a session
webcam = state.currentDevice
@logger.debug("currently selected video device", webcam)
if (webcam? && Object.keys(webcam).length>0)
webcamName = Object.keys(webcam)[0]
webcamName
findChangedWebcams: (newList, oldList) ->
newKeys = Object.keys(newList)
oldKeys = Object.keys(oldList)
webcamSelect = $(@getDOMNode()).find('.webcam-select-container select')
if newKeys.length > oldKeys.length
for newKey in newKeys
if oldKeys.indexOf(newKey) == -1
newWebcam = newList[newKey]
@logger.debug("new webcam found: " + newWebcam, newKey)
context.JK.prodBubble(webcamSelect, 'new-webcam-found', {name: newWebcam}, {positions:['right']})
break
else if newKeys.length < oldKeys.length
for oldKey in oldKeys
if newKeys.indexOf(oldKey) == -1
oldWebcam = oldList[oldKey]
@logger.debug("webcam no longer found: " + oldWebcam)
context.JK.prodBubble(webcamSelect, 'old-webcam-lost', {name: oldWebcam}, {positions:['right']})
break
}
)

View File

@ -95,7 +95,7 @@
// ask the backend to start the session.
var groupedTracks = groupTracksToClient(recording);
jamClient.StartRecording(recording["id"], groupedTracks);
jamClient.StartRecording(recording["id"], groupedTracks, 0, false, 0);
})
.fail(function(jqXHR) {
var details = { clientId: app.clientId, reason: 'rest', detail: arguments, isRecording: false }

View File

@ -59,7 +59,7 @@
if(!checkoutUtils.hasOneFreeItemInShoppingCart(carts)) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemComplete page")
window.location = '/client#/jamtrackBrowse'
window.location = '/client#/jamtrack/search'
}
else {
// ok, we have one, free item. save it for
@ -226,7 +226,7 @@
$backBtn.on('click', function(e) {
e.preventDefault();
context.location = '/client#/jamtrackBrowse'
context.location = '/client#/jamtrack/search'
})
}

View File

@ -27,11 +27,11 @@
var $signinLink = null;
function beforeShow(data) {
renderLoggedInState();
}
function afterShow(data) {
renderLoggedInState();
}
@ -67,13 +67,13 @@
if(carts.length == 0) {
// nothing is in the user's shopping cart. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
logger.error("invalid access of redeemJamTrack page; none")
window.location = '/client#/jamtrack/search'
}
else if(carts.length > 1) {
// the user has multiple items in their shopping cart. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
logger.error("invalid access of redeemJamTrack page; multiple")
window.location = '/client#/jamtrack/search'
}
else {
var item = carts[0];
@ -86,8 +86,8 @@
}
else {
// the user has a non-free, single item in their basket. They shouldn't be here.
logger.error("invalid access of redeemJamTrack page")
window.location = '/client#/jamtrackBrowse'
logger.error("invalid access of redeemJamTrack page, non-free/item")
window.location = '/client#/jamtrack/search'
}
}

View File

@ -224,7 +224,7 @@
var instrumentLogoHtml = '';
if (instruments !== undefined) {
for (var i=0; i < instruments.length; i++) {
var inst = '../assets/content/icon_instrument_default24.png';
var inst = '/assets/content/icon_instrument_default24.png';
if (instruments[i].instrument_id in instrument_logo_map) {
inst = instrument_logo_map[instruments[i].instrument_id].asset;
instrumentLogoHtml += '<img src="' + inst + '" width="24" height="24" />&nbsp;';

View File

@ -3294,8 +3294,8 @@
$voiceChat = $screen.find('#voice-chat');
$tracksHolder = $screen.find('#tracks')
if(gon.global.video_available && gon.global.video_available!="none") {
webcamViewer.init($("#create-session-layout .webcam-container"))
webcamViewer.setVideoOff()
//webcamViewer.init($("#create-session-layout .webcam-container"), false)
//webcamViewer.setVideoOff()
}
events();

View File

@ -22,7 +22,7 @@
};
sessionUtils.setAutoOpenJamTrack = function(jamTrack) {
logger.debug("setting auto-load jamtrack")
logger.debug("setting auto-load jamtrack", jamTrack)
autoOpenJamTrack = jamTrack;
}
@ -30,6 +30,7 @@
sessionUtils.grabAutoOpenJamTrack = function() {
var jamTrack = autoOpenJamTrack;
autoOpenJamTrack = null;
logger.debug("grabbing auto-load jamtrack", jamTrack)
return jamTrack;
}

View File

@ -1081,8 +1081,11 @@
});
}
context.JK.dropdown = function ($select) {
context.JK.dropdown = function ($select, options) {
var opts = options || {}
opts = $.extend({}, {nativeTouch: !(context.jamClient && context.jamClient.IsNativeClient()) && gon.global.env != "test", cutOff: 7}, opts)
$select.each(function (index) {
var $item = $(this);
@ -1090,7 +1093,7 @@
// if this has already been initialized, re-init it so it picks up any new <options>
$item.easyDropDown('destroy')
}
$item.easyDropDown({nativeTouch: !(context.jamClient && context.jamClient.IsNativeClient()) && gon.global.env != "test", cutOff: 7});
$item.easyDropDown(opts);
})
}

View File

@ -12,10 +12,33 @@
var $jamTracksButton = null;
var $ctaJamTracksButton = null;
function computeWeight (jam_track_track, instrument) {
var weight;
if (jam_track_track.track_type == 'Master') {
weight = 0
}
else if (jam_track_track.instrument.id == instrument) {
weight = 1 + jam_track_track.position
}
else {
weight = 10000 + jam_track_track.position
}
return weight;
}
function fetchJamTrack() {
rest.getJamTrackWithArtistInfo({plan_code: gon.jam_track_plan_code})
.done(function (jam_track) {
if(gon.instrument_id) {
jam_track.tracks.sort(function(a, b) {
var aWeight = computeWeight(a, gon.instrument_id)
var bWeight = computeWeight(b, gon.instrument_id)
return aWeight - bWeight
})
}
context._.each(jam_track.tracks, function (track) {
var $element = $('<div class="jam-track-preview-holder"></div>')

View File

@ -23,9 +23,9 @@
logger.debug("jam_track", jam_track)
$jamtrack_band.text(jam_track.original_artist)
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/search')
$jamTracksButton.removeClass('hidden').text("Preview all " + jam_track.band_jam_track_count + " of our " + jam_track.original_artist + " JamTracks")
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/searche')
context._.each(jam_track.tracks, function (track) {

View File

@ -24,7 +24,7 @@
if(!gon.just_previews) {
if (gon.generic) {
$genericHeader.removeClass('hidden');
$jamTracksButton.attr('href', '/client#/jamtrackBrowse')
$jamTracksButton.attr('href', '/client#/jamtrack/search')
$jamTracksButton.removeClass('hidden').text("Check out all 100+ JamTracks")
}
@ -32,9 +32,9 @@
$individualizedHeader.removeClass('hidden')
$jamtrack_name.text('"' + jam_track.name + '"');
$jamtrack_band.text(jam_track.original_artist)
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$jamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/search')
$jamTracksButton.removeClass('hidden').text("Preview all " + jam_track.band_jam_track_count + " of our " + jam_track.original_artist + " JamTracks")
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrackBrowse')
$ctaJamTracksButton.attr('href', '/client?artist=' + jam_track.original_artist + '#/jamtrack/search')
}
}

View File

@ -10,8 +10,6 @@ class Tracking
@rest = new context.JK.Rest();
adTrack: (app) =>
utmSource = $.QueryString['utm_source']
if utmSource == 'facebook-ads' || utmSource == 'google-ads' || utmSource == 'twitter-ads' || utmSource == 'affiliate' || utmSource == 'pr'
if !context.jamClient.IsNativeClient()
if context.JK.currentUserId?
app.user().done( (user) =>
@ -23,12 +21,12 @@ class Tracking
@logger.debug("new user recorded")
context.JK.GA.virtualPageView('/landing/jamtracks/new-user/')
)
else if $.cookie('jamkazam_user')?
@logger.debug("existing/logged out user")
context.JK.GA.virtualPageView('/landing/jamtracks/existing-user/')
else
@logger.debug("new user recorded")
context.JK.GA.virtualPageView('/landing/jamtracks/new-user/')
else
@logger.debug("existing user recorded")
context.JK.GA.virtualPageView('/landing/jamtracks/existing-user/');
jamtrackBrowseTrack: (app) =>
if context.JK.currentUserId?

View File

@ -2,6 +2,40 @@ $ = jQuery
context = window
context.JK ||= {};
ALERT_NAMES = context.JK.ALERT_NAMES;
BackendToFrontend = {
1 : "CIF (352x288)",
2 : "VGA (640x480)",
3 : "4CIF (704x576)",
4 : "1/2 720p HD (640x360)",
5 : "720p HD (1280x720)",
6 : "1080p HD (1920x1080)"
}
BackendNumericToBackendString = {
1 : "CIF (352X288)",
2 : "VGA (640X480)",
3 : "4CIF (704X576)",
4 : "1/2WHD (640X360)",
5 : "WHD (1280X720)",
6 : "FHD (1920x1080)"
}
BackendToFrontendFPS = {
1: 30,
2: 24,
3: 20,
4: 15,
5: 10
}
FrontendToBackend = {}
for key, value of BackendToFrontend
FrontendToBackend[value] = key
context.JK.WebcamViewer = class WebcamViewer
constructor: (@root) ->
@client = context.jamClient
@ -10,23 +44,34 @@ context.JK.WebcamViewer = class WebcamViewer
@toggleBtn = null
@webcamSelect = null
@resolutionSelect = null
@videoShared=false
@resolution=null
@videoShared = false
@resolution = null
@videoSettingsHelp = null
@showBackBtn = false
@rescanTimeout = null
@lastDeviceList = null
init: (root) =>
# the session usage of webcamViewer does not actually pass in anything
root = $() unless root?
@root = root
init: (@root, @showBackButton) =>
@toggleBtn = @root.find(".webcam-test-btn")
@webcamSelect = @root.find(".webcam-select-container select")
@resolutionSelect = @root.find(".webcam-resolution-select-container select")
@webcamSelect.on("change", this.selectWebcam)
@videoSettingsHelp = @root.find('.ftue-video-settings-help')
@rescanningNotice = @root.find('.rescanning-notice')
@backBtn = @root.find('.back-btn')
@webcamSelect.on("change", @selectWebcam)
@toggleBtn.on('click', @toggleWebcam)
@resolutionSelect.on("change", this.selectResolution)
@resolutionSelect.on("change", @selectResolution)
@backBtn.on('click', @back)
@backBtn.show() if @showBackBtn
#logger.debug("Initialed with (unique) select",@webcamSelect)
context.JK.helpBubble(@videoSettingsHelp, 'ftue-video-settings', {}, {width:300}) if @videoSettingsHelp.length > 0
@videoSettingsHelp.click(false)
beforeShow:() =>
@videoShared = false # video can be assumed to be closed before htis is reached
this.loadWebCams()
this.selectWebcam()
this.loadResolutions()
@ -35,30 +80,78 @@ context.JK.WebcamViewer = class WebcamViewer
# protect against non-video clients pointed at video-enabled server from getting into a session
if @client.SessStopVideoSharing
@client.SessStopVideoSharing()
context.JK.onBackendEvent(ALERT_NAMES.USB_CONNECTED, 'webcam-viewer', @onUsbDeviceConnected);
context.JK.onBackendEvent(ALERT_NAMES.USB_DISCONNECTED, 'webcam-viewer', @onUsbDeviceDisconnected);
#client.SessSetInsetPosition(5)
#client.SessSetInsetSize(1)
#client.FTUESetAutoSelectVideoLayout(false)
#client.SessSelectVideoDisplayLayoutGroup(1)
onUsbDeviceConnected: () =>
# don't handle USB events when minimized
return if !context.jamClient.IsFrontendVisible()
logger.debug("USB device connected");
@scheduleRescanSystem(3000);
onUsbDeviceDisconnected:() =>
# don't handle USB events when minimized
return if !context.jamClient.IsFrontendVisible()
logger.debug("USB device disconnected");
@scheduleRescanSystem(3000);
scheduleRescanSystem: (time) =>
if @rescanTimeout?
clearTimeout(@rescanTimeout)
@rescanTimeout = null
@rescanningNotice.show()
@rescanTimeout = setTimeout(() =>
@rescanningNotice.hide()
@loadWebCams()
, time)
selectWebcam:(e, data) =>
device = @webcamSelect.val()
if device?
caps = @client.FTUEGetVideoCaptureDeviceCapabilities(device)
@logger.debug("Got capabilities from device", caps, device)
@client.FTUESelectVideoCaptureDevice(device, caps)
result = @client.FTUESelectVideoCaptureDevice(device, caps)
@logger.debug("FTUESelectVideoCaptureDevice result: ", result)
updateBackend: (selectedResolution, selectedFps) =>
@logger.debug 'Selecting webcam resolution: ', selectedResolution
@logger.debug 'Selecting webcam fps: ', selectedFps
@client.FTUESetVideoEncodeResolution selectedResolution
@client.FTUESetSendFrameRates selectedFps
selectResolution:() =>
@logger.debug 'Selecting from res control: ', @resolutionSelect
@resolution = @resolutionSelect.val()
if @resolution?
@logger.debug 'Selecting webcam resolution: ', @resolution
@client.FTUESetVideoEncodeResolution @resolution
bits = @resolution.split('|')
selectedResolution = bits[0]
selectedFps = bits[1]
@updateBackend(selectedResolution, selectedFps)
# if @isVideoShared
# this.setVideoOff()
# this.toggleWebcam()
beforeHide: () =>
if @rescanTimeout?
clearTimeout(@rescanTimeout)
@rescanTimeout = null
@setVideoOff()
setVideoOff:() =>
if this.isVideoShared()
@client.SessStopVideoSharing()
@ -71,6 +164,9 @@ context.JK.WebcamViewer = class WebcamViewer
@toggleBtn.prop 'disabled', true
@toggleBtn.prop 'disabled', !available
back: () =>
window.location = '/client#/account'
toggleWebcam:() =>
@logger.debug 'Toggling webcam from: ', this.isVideoShared(), @toggleBtn
if this.isVideoShared()
@ -79,6 +175,7 @@ context.JK.WebcamViewer = class WebcamViewer
@videoShared = false
else
@toggleBtn.addClass("selected")
alert("HERE?")
@client.SessStartVideoSharing 0
@videoShared = true
@ -86,23 +183,31 @@ context.JK.WebcamViewer = class WebcamViewer
webcamName="None Configured"
# protect against non-video clients pointed at video-enabled server from getting into a session
webcam = if @client.FTUECurrentSelectedVideoDevice? then @client.FTUECurrentSelectedVideoDevice() else null
logger.debug("currently selected video device", webcam)
if (webcam? && Object.keys(webcam).length>0)
webcamName = _.values(webcam)[0]
webcamName = Object.keys(webcam)[0]
webcamName
loadWebCams:() =>
# protect against non-video clients pointed at video-enabled server from getting into a session
devices = if @client.FTUEGetVideoCaptureDeviceNames? then @client.FTUEGetVideoCaptureDeviceNames() else []
devices = if @client.FTUEGetVideoCaptureDeviceNames? then @client.FTUEGetVideoCaptureDeviceNames() else {}
selectedDevice = this.selectedDeviceName()
@logger.debug("webcam devices", devices, selectedDevice)
selectControl = @webcamSelect
context._.each devices, (device) ->
selected = device == selectedDevice
selectControl.empty()
newDeviceList = []
context._.each devices, (deviceName, deviceGuid) ->
selected = deviceName == selectedDevice
option = $('<option/>',
id: device
value: device
text: device)
id: deviceGuid
value: deviceGuid
text: deviceName)
selectControl.append option
@findChangedWebcams(devices, @lastDeviceList) if @lastDeviceList?
@lastDeviceList = devices
selectControl.val selectedDevice
if devices.length == 0
@ -110,16 +215,76 @@ context.JK.WebcamViewer = class WebcamViewer
else
@root.find('.no-webcam-msg').addClass 'hidden'
findChangedWebcams: (newList, oldList) =>
newKeys = Object.keys(newList)
oldKeys = Object.keys(oldList)
@logger.debug("change webcam check", newKeys, oldKeys)
if newKeys.length > oldKeys.length
for newKey in newKeys
if oldKeys.indexOf(newKey) == -1
newWebcam = newList[newKey]
@logger.debug("new webcam found: " + newWebcam, newKey)
context.JK.prodBubble(@webcamSelect, 'new-webcam-found', {name: newWebcam}, {positions:['right']})
break
else if newKeys.length < oldKeys.length
for oldKey in oldKeys
if newKeys.indexOf(oldKey) == -1
oldWebcam = oldList[oldKey]
@logger.debug("webcam no longer found: " + oldWebcam)
context.JK.prodBubble(@webcamSelect, 'old-webcam-lost', {name: oldWebcam}, {positions:['right']})
break
loadResolutions:() =>
# protect against non-video clients pointed at video-enabled server from getting into a session
resolutions = if @client.FTUEGetAvailableEncodeVideoResolutions? then @client.FTUEGetAvailableEncodeVideoResolutions() else {}
frames = if @client.FTUEGetSendFrameRates? then @client.FTUEGetSendFrameRates() else {}
selectControl = @resolutionSelect
@logger.debug 'FOUND THESE RESOLUTIONS', resolutions, selectControl
context._.each resolutions, (value, key, obj) ->
@logger.debug 'FOUND THESE RESOLUTIONS', resolutions
@logger.debug 'FOUND THESE FPS', frames
context._.each resolutions, (resolution, resolutionKey, obj) ->
#{1: "CIF (352X288)", 2: "VGA (640X480)", 3: "4CIF (704X576)", 4: "1/2WHD (640X360)", 5: "WHD (1280X720)", 6: "FHD (1920x1080)"}
context._.each frames, (frame, key, obj) ->
frontendResolution = BackendToFrontend[resolutionKey]
@logger.error("unknown resolution! #{resolution}") unless frontendResolution
value = "#{resolutionKey}|#{frame}"
text = "#{frontendResolution} at #{frame} fps"
option = $('<option/>',
value: value
text: value)
text: text)
selectControl.append option
if @resolution != null and @resolution != ''
selectControl.val(@resolution)
# load current settings from backend
currentResolution = @client.GetCurrentVideoResolution()
currentFrameRate = @client.GetCurrentVideoFrameRate()
autoSelect = false
if currentResolution == 0
@logger.warn("current resolution not specified; defaulting to VGA")
autoSelect = true
currentResolution = 2
if currentFrameRate == 0
autoSelect = true
@logger.warn("current frame rate not specified; defaulting to 30")
currentFrameRate = 30
else
convertedFrameRate = BackendToFrontendFPS[currentFrameRate]
@logger.debug("translating FPS: backend numeric #{currentFrameRate} to #{convertedFrameRate}")
currentFrameRate = convertedFrameRate
selected = currentResolution + '|' + currentFrameRate
# backend needs to be same as frontend
if autoSelect
@updateBackend(currentResolution, currentFrameRate)
@logger.debug("setting current value of video settings to: " + selected)
selectControl.val(selected)

View File

@ -5,18 +5,49 @@
context.JK = context.JK || {}
context.JK.StepVideoGear = function (app, $dialog) {
var $step = null
var $webcamViewer = new context.JK.WebcamViewer()
var webcamViewerReact = null;
var $instructions = null;
var $noWebcamList = null;
function initialize(_$step) {
$step = _$step
$webcamViewer.init($step)
var reactElement = React.createElement(window.WebcamViewer, {isVisible: false});
var reactDomNode = $step.find(".webcam-container").get(0)
webcamViewerReact = React.render(reactElement, reactDomNode)
$instructions = $step.find('.instructions')
$noWebcamList = $step.find('ul.no-webcam')
window.VideoStore.listen(onVideoStoreUpdated);
}
function onVideoStoreUpdated(videoState) {
var noWebcams = Object.keys(videoState.deviceNames).length == 0
var isWindows = window.PlatformStore.isWindows();
$instructions.removeClass('has-webcam no-webcam')
if(noWebcams) {
$instructions.addClass('no-webcam')
}
else {
$instructions.addClass('has-webcam')
}
$noWebcamList.removeClass('is-windows is-not-windows')
if(isWindows) {
$noWebcamList.addClass('is-windows')
}
else {
$noWebcamList.addClass('is-not-windows')
}
}
function beforeShow() {
$webcamViewer.beforeShow()
$dialog.getWizard().getDialog().find('h1.top-header').text('video gear setup')
webcamViewerReact.beforeShow()
}
function beforeHide() {
$webcamViewer.setVideoOff()
$dialog.getWizard().getDialog().find('h1.top-header').text('audio gear setup')
webcamViewerReact.beforeHide()
}
this.beforeShow = beforeShow

Some files were not shown because too many files have changed in this diff Show More