From 9dc364ea20e847a9aabf6b440edf447a0d081b90 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 8 Jan 2015 11:14:02 -0600 Subject: [PATCH 01/46] VRFS-2498 : Metronome graphic, made transparent. Session UI for metronome. --- .../assets/images/content/icon_metronome.png | Bin 0 -> 25498 bytes .../assets/stylesheets/client/session.css.scss | 4 +--- web/app/views/clients/_session.html.erb | 10 +++++++++- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 web/app/assets/images/content/icon_metronome.png diff --git a/web/app/assets/images/content/icon_metronome.png b/web/app/assets/images/content/icon_metronome.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8d963b22ffb1d630445386bb518eea243acf3d GIT binary patch literal 25498 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 >2}!+r~I z)HjYQc<8ag#1ErT%~V`-n94gWbgp3D@*U|0vYnv^X7lSUJRSY~*p!B?KxNpNBVcaj z5aY`=8d@jI7t*VfMF8^s9Urc#*6H%;WbZT$CidbiYgqOEmeF?#^O#xWH3#DPo&63g z!sS)e-DYO={UFIvNQ+f`bl1SNp_C;&sN6m5-eAdHCUn zw{Y|E5NzWH1Ct?`tj~-$-&BlWA_zo)MSR%5X3d&y(B0p{Va#Rh-b96|BlW0bty9fc zcsOVA6;}kN|15IpXds76>K%w=C1w`C;ZG*)N8)JP>U@x69eM7Om6cTrg4)V^QaBD= zp$f>UZL!)Bc@W6nh;gxVy;js*_n?K#DlGav5Y zp#F83zm1(bb?RWs9z=V`dyz`WPl+KA^@l_*+-=MkE@6-L@tDGkZ~|OJ$RH$_9}8lL zDCC4t<}43|i92bM&xJdRvz>zvX8iWsZ%ZhD6Jpw~nDCe4U0ZvXl71%sG>~g56V`c_ zU#!074@`jfAboApWWgm4!YZHQ$xJ;AY|4LJnuRQ{icF24yJ zWj|?VOx%hUDU_cfh7pFo>D@+Ag-cA44<`vACY;2Z@M|t3Bt@<4a46meR-$KNhx7;O zr;*iU5Opf^nx%~RyAoc5ylRnGb)G$VuMHvWjrz3?>gYW{#LA?r&QllVL(j^8WB+2xq>&+q>Vl`0|y9%22rG zHK;x{@!!n1nS`z2`>r5hRs22pUxm6=fEzk+32zdux|&m7R*ro$Kh$??o8lPpD4v}1 z@;k+q6EP^R9QAaVcuOuRwuR#9@h5JZTh zM?_=9l8nwaL^Y)JmtkS{EmRx}D~vCqur{i_R*pC*iU$WvXpd_k)@>s07-04-Tw^F) zV?trdO}Df=4S`t4$gBc{s0bqN4uaHez%8$QJC#-B6ImOmB193w$oa(@OP}t(h(c{N zQBnk{|0ow6lnje84f%`vQr02{u7T^$XF!<@9KK_oR3g;2@%Gq?4gO<`e|_+Bq;jfy$FtXXl{EU=w_#u;Z^%u@>$s>e-AL#a3dyWp|~= zP*7^xg~a)p{}TzGMERpx@m{?nhsh;|as37B#m0P@vh z4pW^YaQ0*ZToaB{mK&5D<~M;{gs@s;(M?Vg*M!(24k5lC{YPsQ5YL4CTD8_UBN8%O zOPTY2_~D0{*gnmMyUE&k5jS})XYL>ZDNT5Fe66@wC6Db8*<$kk9g#{LW5?n~pjMfe zND#;nNF?aQ5vI8dh*%B6s0A|D=Gg=BtO6_HyMe5`@ZW=VE>V=KR;^kY2&FvXRk5h8 z!J?pd78c^rI*4L9M7A1HXd_7fH;*PfTOlTG&R+~c%;r|$xp7o46Ey>Y^oD>qlAiQ# zMW*%{2xK6zb3uSntJp4&*|{K{X>GbeK(wKW=N%a$Nw==c)Hm4>$fb!p{vV1`wXO!p RVygfE002ovPDHLkV1iWBQY-)f literal 0 HcmV?d00001 diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 8c37af233..01a1891ae 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -74,7 +74,7 @@ left:5px; } - .open-media-file-header { + .open-media-file-header, .use-metronome-header { font-size:16px; line-height:100%; margin:0; @@ -319,8 +319,6 @@ table.vu td { color: inherit; } - - .session-add { margin-top:9px; margin-bottom:8px; diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 377ec043d..424174931 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -120,10 +120,18 @@
  • JamTrack
  • <% end %> - + + +
    +
    + <%= image_tag "content/icon_metronome.png", {width:22, height:20} %> + Use Metronome +
    +
    + <%= render "play_controls" %> From 34d798576254607a0f94b914dbfb28d6e9f16757 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 9 Jan 2015 02:47:46 -0600 Subject: [PATCH 02/46] VRFS-2498, VRFS-2499 : Incremental --- web/app/assets/javascripts/sessionModel.js | 18 +++++++ web/app/views/clients/_session.html.erb | 2 +- .../dialogs/_openBackingTrackDialog.html.slim | 50 +++++++++++++++++++ .../dialogs/_openMetronomeDialog.html.slim | 50 +++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 web/app/views/dialogs/_openBackingTrackDialog.html.slim create mode 100644 web/app/views/dialogs/_openMetronomeDialog.html.slim diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 9b7f034fe..d46319c45 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -87,6 +87,24 @@ } } + function backingTracks() { + if(currentSession) { + return currentSession.backing_tracks + } + else { + return null; + } + } + + function metronomeTracks() { + if(currentSession) { + return currentSession.metronome_tracks + } + else { + return null; + } + } + function creatorId() { if(!currentSession) { throw "creator is not known" diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 424174931..fd4276456 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -119,7 +119,7 @@ <% if Rails.application.config.jam_tracks_available %>
  • JamTrack
  • <% end %> - +
  • Audio File

  • diff --git a/web/app/views/dialogs/_openBackingTrackDialog.html.slim b/web/app/views/dialogs/_openBackingTrackDialog.html.slim new file mode 100644 index 000000000..18ca215a7 --- /dev/null +++ b/web/app/views/dialogs/_openBackingTrackDialog.html.slim @@ -0,0 +1,50 @@ +.dialog.openJamTrackDialog-overlay.ftue-overlay.tall#open-jam-track-dialog layout="dialog" layout-id="open-jam-track-dialog" + + .content-head + = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' } + h1 + | open a jamtrack + + .dialog-inner + + .recording-wrapper + table.open-jam-tracks cellspacing="0" cellpadding="0" border="0" + thead + tr + th align="left" + | NAME + th align="left" + | ORIGINAL ARTIST + th align="left" + | DOWNLOADED + a.downloaded-jamtrack-help href="#" + | ? + tbody + + br + + .left.paginator-holder + + .help-links + a.what-are-jamtracks href='#' + | What are JamTracks? + a href='/client#/jamtrack' rel="external" + | Shop for JamTracks + .right + a href="#" class="button-grey" layout-action="close" + | CANCEL + + + br clear="all" + + + script#template-jam-track-row type="text/template" + tr data-recording-id="{{data.jamTrackId}}" data-local-state="{{data.jamTrackState}}" + td + | {{data.name}} + td + | {{data.artist}} + td + | {{data.downloaded}} + + diff --git a/web/app/views/dialogs/_openMetronomeDialog.html.slim b/web/app/views/dialogs/_openMetronomeDialog.html.slim new file mode 100644 index 000000000..18ca215a7 --- /dev/null +++ b/web/app/views/dialogs/_openMetronomeDialog.html.slim @@ -0,0 +1,50 @@ +.dialog.openJamTrackDialog-overlay.ftue-overlay.tall#open-jam-track-dialog layout="dialog" layout-id="open-jam-track-dialog" + + .content-head + = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' } + h1 + | open a jamtrack + + .dialog-inner + + .recording-wrapper + table.open-jam-tracks cellspacing="0" cellpadding="0" border="0" + thead + tr + th align="left" + | NAME + th align="left" + | ORIGINAL ARTIST + th align="left" + | DOWNLOADED + a.downloaded-jamtrack-help href="#" + | ? + tbody + + br + + .left.paginator-holder + + .help-links + a.what-are-jamtracks href='#' + | What are JamTracks? + a href='/client#/jamtrack' rel="external" + | Shop for JamTracks + .right + a href="#" class="button-grey" layout-action="close" + | CANCEL + + + br clear="all" + + + script#template-jam-track-row type="text/template" + tr data-recording-id="{{data.jamTrackId}}" data-local-state="{{data.jamTrackState}}" + td + | {{data.name}} + td + | {{data.artist}} + td + | {{data.downloaded}} + + From 0c1abfba22efdf4b9bea8125e69a6359763eb6a0 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 11 Jan 2015 13:05:55 -0600 Subject: [PATCH 03/46] VRFS-2499 : Schema changes for backing tracks. --- db/manifest | 3 ++- db/up/backing_tracks.sql | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 db/up/backing_tracks.sql diff --git a/db/manifest b/db/manifest index e51930a82..4337b6afa 100755 --- a/db/manifest +++ b/db/manifest @@ -242,4 +242,5 @@ active_jam_track.sql bpms_on_tap_in.sql jamtracks_job.sql text_messages.sql -text_message_migration.sql \ No newline at end of file +text_message_migration.sql +backing_tracks.sql \ No newline at end of file diff --git a/db/up/backing_tracks.sql b/db/up/backing_tracks.sql new file mode 100644 index 000000000..54552762c --- /dev/null +++ b/db/up/backing_tracks.sql @@ -0,0 +1,2 @@ +ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_id BIGINT; +ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_filename VARCHAR(1024); From f6111516fdc0c7aae5a81734c61f018a4be9c120 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 11 Jan 2015 13:42:42 -0600 Subject: [PATCH 04/46] VRFS-2499 : Metronome schema changes. --- db/manifest | 3 ++- db/up/metronome.sql | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 db/up/metronome.sql diff --git a/db/manifest b/db/manifest index 4337b6afa..161ba7501 100755 --- a/db/manifest +++ b/db/manifest @@ -243,4 +243,5 @@ bpms_on_tap_in.sql jamtracks_job.sql text_messages.sql text_message_migration.sql -backing_tracks.sql \ No newline at end of file +backing_tracks.sql +metronome.sql \ No newline at end of file diff --git a/db/up/metronome.sql b/db/up/metronome.sql new file mode 100644 index 000000000..d074c180c --- /dev/null +++ b/db/up/metronome.sql @@ -0,0 +1,2 @@ +ALTER TABLE active_music_sessions ADD COLUMN metronome_id BIGINT; +ALTER TABLE active_music_sessions ADD COLUMN metronome_initiator_id VARCHAR(64); \ No newline at end of file From d0c0c6d01e51ff10ade016339ba06b1590461ce3 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 11 Jan 2015 14:15:47 -0600 Subject: [PATCH 05/46] VRFS-2498, VRFS-2499 : Validations. --- db/up/backing_tracks.sql | 4 +- .../jam_ruby/constants/validation_messages.rb | 2 + .../jam_ruby/models/active_music_session.rb | 51 +++++++++++++++++-- web/app/views/clients/_session.html.erb | 2 +- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/db/up/backing_tracks.sql b/db/up/backing_tracks.sql index 54552762c..cac4b399d 100644 --- a/db/up/backing_tracks.sql +++ b/db/up/backing_tracks.sql @@ -1,2 +1,2 @@ -ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_id BIGINT; -ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_filename VARCHAR(1024); +ALTER TABLE active_music_sessions ADD COLUMN backing_track_id VARCHAR(1024); +ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_id VARCHAR(64); diff --git a/ruby/lib/jam_ruby/constants/validation_messages.rb b/ruby/lib/jam_ruby/constants/validation_messages.rb index c24ca07ce..662fce608 100644 --- a/ruby/lib/jam_ruby/constants/validation_messages.rb +++ b/ruby/lib/jam_ruby/constants/validation_messages.rb @@ -82,6 +82,8 @@ module ValidationMessages MUST_BE_KNOWN_TIMEZONE = "not valid" JAM_TRACK_ALREADY_OPEN = 'another jam track already open' RECORDING_ALREADY_IN_PROGRESS = "recording being made" + METRONOME_ALREADY_OPEN = 'another metronome already open' + BACKING_TRACK_ALREADY_OPEN = 'another audio file already open' # notification DIFFERENT_SOURCE_TARGET = 'can\'t be same as the sender' diff --git a/ruby/lib/jam_ruby/models/active_music_session.rb b/ruby/lib/jam_ruby/models/active_music_session.rb index c28ae8de2..16ae93748 100644 --- a/ruby/lib/jam_ruby/models/active_music_session.rb +++ b/ruby/lib/jam_ruby/models/active_music_session.rb @@ -7,7 +7,7 @@ module JamRuby self.table_name = 'active_music_sessions' - attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording + attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id" @@ -15,6 +15,9 @@ module JamRuby belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :foreign_key => "jam_track_id", :inverse_of => :playing_sessions belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "jam_track_initiator_id" + belongs_to :backing_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "backing_track_initiator_id" + belongs_to :metronome_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "metronome_initiator_id" + has_one :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => 'music_session_id' has_one :mount, :class_name => "JamRuby::IcecastMount", :inverse_of => :music_session, :foreign_key => 'music_session_id' belongs_to :creator, :class_name => 'JamRuby::User', :foreign_key => :user_id @@ -27,6 +30,8 @@ module JamRuby validate :creator_is_musician validate :validate_opening_recording, :if => :opening_recording validate :validate_opening_jam_track, :if => :opening_jam_track + validate :validate_opening_backing_track, :if => :opening_backing_track + validate :validate_opening_metronome, :if => :opening_metronome after_create :started_session @@ -73,22 +78,52 @@ module JamRuby if is_jam_track_open? errors.add(:claimed_recording, ValidationMessages::JAM_TRACK_ALREADY_OPEN) end + + if is_backing_track_open? + errors.add(:claimed_recording, ValidationMessages::BACKING_TRACK_ALREADY_OPEN) + end + + if is_metronome_open? + errors.add(:claimed_recording, ValidationMessages::METRONOME_ALREADY_OPEN) + end end def validate_opening_jam_track + validate_other_audio(:jam_track) + end + + def validate_opening_backing_track + validate_other_audio(:backing_track) + end + + def validate_opening_metronome + validate_other_audio(:metronome) + end + + def validate_other_audio(error_key) + # validate that there is no metronome already open in this session + unless metronome_id_was.nil? + errors.add(error_key, ValidationMessages::METRONOME_ALREADY_OPEN) + end + + # validate that there is no backing track already open in this session + unless backing_track_id_was.nil? + errors.add(error_key, ValidationMessages::BACKING_TRACK_ALREADY_OPEN) + end + # validate that there is no jam track already open in this session unless jam_track_id_was.nil? - errors.add(:jam_track, ValidationMessages::JAM_TRACK_ALREADY_OPEN) + errors.add(error_key, ValidationMessages::JAM_TRACK_ALREADY_OPEN) end # validate that there is no recording being made if is_recording? - errors.add(:jam_track, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS) + errors.add(error_key, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS) end # validate that there is no recording being played back to the session if is_playing_recording? - errors.add(:jam_track, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS) + errors.add(error_key, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS) end end @@ -593,6 +628,14 @@ module JamRuby !self.jam_track.nil? end + def is_backing_track_open? + self.backing_track_id.present? + end + + def is_metronome_open? + self.metronome_id.present? + end + # is this music session currently recording? def is_recording? recordings.where(:duration => nil).count > 0 diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index fd4276456..5f51e17ed 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -119,7 +119,7 @@ <% if Rails.application.config.jam_tracks_available %>
  • JamTrack
  • <% end %> -
  • Audio File
  • +
  • Audio File

  • From c6f03a91b85c02ec3e3c60eb498f2f2e53403905 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 12 Jan 2015 17:44:20 -0600 Subject: [PATCH 06/46] VRFS-2498, VRFS-2618 More coherent data model and logic for backing tracks and metronome re: active_music_session_spec. --- db/up/backing_tracks.sql | 2 +- db/up/metronome.sql | 2 +- .../jam_ruby/models/active_music_session.rb | 39 ++++- .../models/active_music_session_spec.rb | 162 ++++++++++++++++++ 4 files changed, 198 insertions(+), 7 deletions(-) diff --git a/db/up/backing_tracks.sql b/db/up/backing_tracks.sql index cac4b399d..328b36820 100644 --- a/db/up/backing_tracks.sql +++ b/db/up/backing_tracks.sql @@ -1,2 +1,2 @@ -ALTER TABLE active_music_sessions ADD COLUMN backing_track_id VARCHAR(1024); +ALTER TABLE active_music_sessions ADD COLUMN backing_track_path VARCHAR(1024); ALTER TABLE active_music_sessions ADD COLUMN backing_track_initiator_id VARCHAR(64); diff --git a/db/up/metronome.sql b/db/up/metronome.sql index d074c180c..25d18dc57 100644 --- a/db/up/metronome.sql +++ b/db/up/metronome.sql @@ -1,2 +1,2 @@ -ALTER TABLE active_music_sessions ADD COLUMN metronome_id BIGINT; +ALTER TABLE active_music_sessions ADD COLUMN metronome_active BOOLEAN NOT NULL DEFAULT FALSE; ALTER TABLE active_music_sessions ADD COLUMN metronome_initiator_id VARCHAR(64); \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/active_music_session.rb b/ruby/lib/jam_ruby/models/active_music_session.rb index 16ae93748..5e6ed8036 100644 --- a/ruby/lib/jam_ruby/models/active_music_session.rb +++ b/ruby/lib/jam_ruby/models/active_music_session.rb @@ -102,17 +102,17 @@ module JamRuby def validate_other_audio(error_key) # validate that there is no metronome already open in this session - unless metronome_id_was.nil? + if metronome_active_was errors.add(error_key, ValidationMessages::METRONOME_ALREADY_OPEN) end # validate that there is no backing track already open in this session - unless backing_track_id_was.nil? + if backing_track_path_was.present? errors.add(error_key, ValidationMessages::BACKING_TRACK_ALREADY_OPEN) end # validate that there is no jam track already open in this session - unless jam_track_id_was.nil? + if jam_track_id_was.present? errors.add(error_key, ValidationMessages::JAM_TRACK_ALREADY_OPEN) end @@ -629,11 +629,11 @@ module JamRuby end def is_backing_track_open? - self.backing_track_id.present? + self.backing_track_path.present? end def is_metronome_open? - self.metronome_id.present? + self.metronome_active.present? end # is this music session currently recording? @@ -785,6 +785,35 @@ module JamRuby self.save end + # @param backing_track_path is a relative path: + def open_backing_track(user, backing_track_path) + self.backing_track_path = backing_track_path + self.backing_track_initiator = user + self.opening_backing_track = true + self.save + self.opening_backing_track = false + end + + def close_backing_track + self.backing_track_path = nil + self.backing_track_initiator = nil + self.save + end + + def open_metronome(user) + self.metronome_active = true + self.metronome_initiator = user + self.opening_metronome = true + self.save + self.opening_metronome = false + end + + def close_metronome + self.metronome_active = false + self.metronome_initiator = nil + self.save + end + def self.sync(session_history) music_session = MusicSession.find_by_id(session_history.id) diff --git a/ruby/spec/jam_ruby/models/active_music_session_spec.rb b/ruby/spec/jam_ruby/models/active_music_session_spec.rb index 392d70473..ec160d0bb 100644 --- a/ruby/spec/jam_ruby/models/active_music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/active_music_session_spec.rb @@ -745,6 +745,29 @@ describe ActiveMusicSession do @music_session.errors[:claimed_recording] == [ValidationMessages::JAM_TRACK_ALREADY_OPEN] end + + it "disallow a claimed recording to be started when backing track is open" do + # open the backing track + @backing_track = "foo.mp3" + @music_session.open_backing_track(@user1, @backing_track) + @music_session.errors.any?.should be_false + + # and try to open a recording for playback + @music_session.claimed_recording_start(@user1, @claimed_recording) + @music_session.errors.any?.should be_true + @music_session.errors[:claimed_recording] == [ValidationMessages::BACKING_TRACK_ALREADY_OPEN] + end + + it "disallow a claimed recording to be started when metronome is open" do + # open the metronome + @music_session.open_metronome(@user1) + @music_session.errors.any?.should be_false + + # and try to open a recording for playback + @music_session.claimed_recording_start(@user1, @claimed_recording) + @music_session.errors.any?.should be_true + @music_session.errors[:claimed_recording] == [ValidationMessages::METRONOME_ALREADY_OPEN] + end end end @@ -830,5 +853,144 @@ describe ActiveMusicSession do music_sessions[0].connections[0].tracks.should have(1).items end end + + describe "open_backing_track" do + before(:each) do + @user1 = FactoryGirl.create(:user) + @connection = FactoryGirl.create(:connection, :user => @user1) + @instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') + @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) + @music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true) + # @music_session.connections << @connection + @music_session.save! + @connection.join_the_session(@music_session, true, nil, @user1, 10) + @backing_track = "foo/bar.mp3" + end + + it "allow a backing track to be associated" do + # simple success case; just open the backing track and observe the state of the session is correct + @music_session.open_backing_track(@user1, @backing_track) + @music_session.errors.any?.should be_false + @music_session.reload + @music_session.backing_track_path.should == @backing_track + @music_session.backing_track_initiator.should == @user1 + end + + it "allow a backing track to be closed" do + # simple success case; close an opened backing track and observe the state of the session is correct + @music_session.open_backing_track(@user1, @backing_track) + @music_session.errors.any?.should be_false + @music_session.close_backing_track + @music_session.errors.any?.should be_false + @music_session.reload + @music_session.backing_track_path.should be_nil + @music_session.backing_track_initiator.should be_nil + end + + it "disallow a backing track to be opened when another is already opened" do + # if a backing track is open, don't allow another to be opened + @music_session.open_backing_track(@user1, @backing_track) + @music_session.errors.any?.should be_false + @music_session.open_backing_track(@user1, @backing_track) + @music_session.errors.any?.should be_true + @music_session.errors[:backing_track] == [ValidationMessages::BACKING_TRACK_ALREADY_OPEN] + end + + it "disallow a backing track to be opened when recording is ongoing" do + @recording = Recording.start(@music_session, @user1) + @music_session.errors.any?.should be_false + @music_session.open_backing_track(@user1, @backing_track) + @music_session.errors.any?.should be_true + @music_session.errors[:backing_track] == [ValidationMessages::RECORDING_ALREADY_IN_PROGRESS] + end + + it "disallow a backing track to be opened when recording is playing back" do + # create a recording, and open it for play back + @recording = Recording.start(@music_session, @user1) + @recording.errors.any?.should be_false + @recording.stop + @recording.reload + @claimed_recording = @recording.claim(@user1, "name", "description", Genre.first, true) + @claimed_recording.errors.any?.should be_false + @music_session.claimed_recording_start(@user1, @claimed_recording) + @music_session.errors.any?.should be_false + + # while it's open, try to open a jam track + @music_session.open_backing_track(@user1, @backing_track) + @music_session.errors.any?.should be_true + @music_session.errors[:backing_track] == [ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS] + end + + end + + describe "open_metronome" do + before(:each) do + @user1 = FactoryGirl.create(:user) + @connection = FactoryGirl.create(:connection, :user => @user1) + @instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') + @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) + @music_session = FactoryGirl.create(:active_music_session, :creator => @user1, :musician_access => true) + # @music_session.connections << @connection + @music_session.save! + @connection.join_the_session(@music_session, true, nil, @user1, 10) + end + + it "allow a metronome to be activated" do + # simple success case; just open the metronome and observe the state of the session is correct + @music_session.open_metronome(@user1) + puts "@music_session.errors: #{@music_session.errors.inspect}" + @music_session.errors.any?.should be_false + @music_session.reload + @music_session.metronome_active.should == true + @music_session.metronome_initiator.should == @user1 + end + + it "allow a metronome to be closed" do + # simple success case; close an opened metronome and observe the state of the session is correct + @music_session.open_metronome(@user1) + @music_session.errors.any?.should be_false + @music_session.close_metronome + @music_session.errors.any?.should be_false + @music_session.reload + @music_session.metronome_active.should be_false + @music_session.metronome_initiator.should be_nil + end + + it "disallow a metronome to be opened when another is already opened" do + # if a metronome is open, don't allow another to be opened + @music_session.open_metronome(@user1) + @music_session.errors.any?.should be_false + @music_session.open_metronome(@user1) + @music_session.errors.any?.should be_true + @music_session.errors[:metronome] == [ValidationMessages::METRONOME_ALREADY_OPEN] + end + + it "disallow a metronome to be opened when recording is ongoing" do + @recording = Recording.start(@music_session, @user1) + @music_session.errors.any?.should be_false + @music_session.open_metronome(@user1) + @music_session.errors.any?.should be_true + @music_session.errors[:metronome] == [ValidationMessages::RECORDING_ALREADY_IN_PROGRESS] + end + + it "disallow a metronome to be opened when recording is playing back" do + # create a recording, and open it for play back + @recording = Recording.start(@music_session, @user1) + @recording.errors.any?.should be_false + @recording.stop + @recording.reload + @claimed_recording = @recording.claim(@user1, "name", "description", Genre.first, true) + @claimed_recording.errors.any?.should be_false + @music_session.claimed_recording_start(@user1, @claimed_recording) + @music_session.errors.any?.should be_false + + # while it's open, try to open a jam track + @music_session.open_metronome(@user1) + @music_session.errors.any?.should be_true + @music_session.errors[:metronome] == [ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS] + end + + end + end From 4f94444429d629b75056ac221cbcc991be36a9ca Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 13 Jan 2015 10:15:46 -0600 Subject: [PATCH 07/46] VRFS-2498, VRFS-2618 : API, routes and specs for backing tracks/metronomes. --- .../api_music_sessions_controller.rb | 40 ++++++++- web/config/routes.rb | 4 + .../api_music_sessions_controller_spec.rb | 83 +++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index b85f265d0..20d34b823 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -4,7 +4,7 @@ class ApiMusicSessionsController < ApiController # have to be signed in currently to see this screen before_filter :api_signed_in_user, :except => [ :add_like, :show, :show_history, :add_session_info_comment ] - before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop, :track_sync, :jam_track_open, :jam_track_close] + before_filter :lookup_session, only: [:show, :update, :delete, :claimed_recording_start, :claimed_recording_stop, :track_sync, :jam_track_open, :jam_track_close, :backing_track_open, :backing_track_close, :metronome_open, :metronome_close] skip_before_filter :api_signed_in_user, only: [:perf_upload] respond_to :json @@ -597,8 +597,44 @@ class ApiMusicSessionsController < ApiController respond_with_model(@music_session) end + def backing_track_open + unless @music_session.users.exists?(current_user) + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end - private + @backing_track_path = params[:backing_track_path] + @music_session.open_backing_track(current_user, @backing_track_path) + respond_with_model(@music_session) + end + + def backing_track_close + unless @music_session.users.exists?(current_user) + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end + + @music_session.close_backing_track() + respond_with_model(@music_session) + end + + def metronome_open + unless @music_session.users.exists?(current_user) + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end + + @music_session.open_metronome(current_user) + respond_with_model(@music_session) + end + + def metronome_close + unless @music_session.users.exists?(current_user) + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR + end + + @music_session.close_metronome() + respond_with_model(@music_session) + end + +private def lookup_session @music_session = ActiveMusicSession.find(params[:id]) diff --git a/web/config/routes.rb b/web/config/routes.rb index 2a18c7fce..befe50ffb 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -178,6 +178,10 @@ SampleApp::Application.routes.draw do match '/sessions/:id/details/comments' => 'api_music_sessions#add_session_info_comment', :via => :post match '/sessions/:id/jam_tracks/:jam_track_id/open' => 'api_music_sessions#jam_track_open', :via => :post match '/sessions/:id/jam_tracks/close' => 'api_music_sessions#jam_track_close', :via => :post + match '/sessions/:id/backing_tracks/:backing_track_id/open' => 'api_music_sessions#backing_track_open', :via => :post + match '/sessions/:id/backing_tracks/close' => 'api_music_sessions#backing_track_close', :via => :post + match '/sessions/:id/jam_tracks/:metronome_id/open' => 'api_music_sessions#metronome_open', :via => :post + match '/sessions/:id/metronomes/close' => 'api_music_sessions#metronome_close', :via => :post # music session tracks match '/sessions/:id/tracks' => 'api_music_sessions#track_create', :via => :post diff --git a/web/spec/controllers/api_music_sessions_controller_spec.rb b/web/spec/controllers/api_music_sessions_controller_spec.rb index eab77192b..27565dd55 100644 --- a/web/spec/controllers/api_music_sessions_controller_spec.rb +++ b/web/spec/controllers/api_music_sessions_controller_spec.rb @@ -223,4 +223,87 @@ describe ApiMusicSessionsController do json[:jam_track][:id].should == jam_track.id end end + + describe "open_backing_track" do + + let(:ams) { FactoryGirl.create(:active_music_session, creator: user) } + let(:backing_track) { "foo.mp3"} + + it "does not allow someone to open a track unless they are in the session" do + post :backing_track_open, {:format => 'json', id: ams.id, backing_track_path: backing_track} + response.status.should == 403 + end + + it "does not allow someone to open a track unless they own the backing track" do + pending "connection with client to determine ownership" + conn.join_the_session(ams.music_session, true, tracks, user, 10) + + post :backing_track_open, {:format => 'json', id: ams.id, backing_track_path: backing_track} + response.status.should == 403 + end + + it "allows someone who owns the backing track to open it" do + # put the connection of the user into the session, so th + conn.join_the_session(ams.music_session, true, tracks, user, 10) + + post :backing_track_open, {:format => 'json', id: ams.id, backing_track_path: backing_track} + response.status.should == 200 + end + + it "does not allow someone to close a track unless they are in the session" do + post :backing_track_close, {:format => 'json', id: ams.id} + response.status.should == 403 + end + + it "allows the backing track to be closed" do + # put the connection of the user into the session, so th + conn.join_the_session(ams.music_session, true, tracks, user, 10) + + post :backing_track_open, {:format => 'json', id: ams.id, backing_track_path: backing_track} + response.status.should == 200 + + post :backing_track_close, {:format => 'json', id: ams.id} + response.status.should == 200 + end + end + + describe "open_metronome" do + + let(:ams) { FactoryGirl.create(:active_music_session, creator: user) } + let(:metronome) { "foo.mp3"} + + it "does not allow someone to open a track unless they are in the session" do + post :metronome_open, {:format => 'json', id: ams.id, metronome_path: metronome} + response.status.should == 403 + end + + it "can open it" do + conn.join_the_session(ams.music_session, true, tracks, user, 10) + + post :metronome_open, {:format => 'json', id: ams.id, metronome_path: metronome} + response.status.should == 200 + end + + it "does not allow someone to close a metronome" do + post :metronome_close, {:format => 'json', id: ams.id} + response.status.should == 403 + end + + it "does allow someone who joied to close a metronome" do + conn.join_the_session(ams.music_session, true, tracks, user, 10) + post :metronome_close, {:format => 'json', id: ams.id} + response.status.should == 200 + end + + it "allows the metronome to be closed" do + # put the connection of the user into the session, so th + conn.join_the_session(ams.music_session, true, tracks, user, 10) + + post :metronome_open, {:format => 'json', id: ams.id, metronome_path: metronome} + response.status.should == 200 + + post :metronome_close, {:format => 'json', id: ams.id} + response.status.should == 200 + end + end end From 3fb45c0dd0c92c37129c08b0f554f28e572e3e87 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 15 Jan 2015 20:28:34 -0600 Subject: [PATCH 08/46] VRFS-2499 - Incremental, nearly done. --- .../dialog/openBackingTrackDialog.js | 137 ++++++++++++++++ web/app/assets/javascripts/jam_rest.js | 37 +++++ web/app/assets/javascripts/session.js | 150 +++++++++++++++++- web/app/assets/javascripts/sessionModel.js | 8 +- .../stylesheets/client/sessionList.css.scss | 4 +- .../dialogs/openBackingTrackDialog.css.scss | 44 +++++ .../api_backing_tracks_controller.rb | 17 ++ web/app/views/api_backing_tracks/index.rabl | 7 + web/app/views/clients/_session.html.erb | 2 +- web/app/views/clients/index.html.erb | 3 + web/app/views/dialogs/_dialogs.html.haml | 1 + .../dialogs/_openBackingTrackDialog.html.slim | 36 ++--- .../dialogs/_openMetronomeDialog.html.slim | 50 ------ web/config/routes.rb | 5 +- 14 files changed, 422 insertions(+), 79 deletions(-) create mode 100644 web/app/assets/javascripts/dialog/openBackingTrackDialog.js create mode 100644 web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss create mode 100644 web/app/controllers/api_backing_tracks_controller.rb create mode 100644 web/app/views/api_backing_tracks/index.rabl delete mode 100644 web/app/views/dialogs/_openMetronomeDialog.html.slim diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js new file mode 100644 index 000000000..905d6d9e9 --- /dev/null +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -0,0 +1,137 @@ +(function(context,$) { + + "use strict"; + context.JK = context.JK || {}; + context.JK.OpenBackingTrackDialog = function(app) { + var logger = context.JK.logger; + var rest = context.JK.Rest(); + var showing = false; + var perPage = 10; + var $dialog = null; + var $tbody = null; + var $paginatorHolder = null; + var $templateOpenBackingTrackRow = null; + var $downloadedTrackHelp = null; + var $whatAreBackingTracks = null; + + + function emptyList() { + $tbody.empty(); + } + + function resetPagination() { + $dialog.find('.paginator').remove(); + } + + function beforeShow() { + emptyList(); + resetPagination(); + showing = true; + getBackingTracks() + .done(function(data, textStatus, jqXHR) { + // initialize pagination + var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected) + $paginatorHolder.append($paginator); + }); + } + + function afterHide() { + showing = false; + } + + + function onPageSelected(targetPage) { + return getBackingTracks(targetPage); + } + + function getBackingTracks(page) { + return rest.getBackingTracks({page:page + 1, per_page:10}) + .done(function(result) { + console.log("result: ", result) + var backingTracks = result.backing_tracks + console.log("Backing Tracks: ", backingTracks) + emptyList(); + + $.each(backingTracks, function(index, backingTrack) { + console.log("Backing TRACK: ", backingTrack) + var options = { + backingTrackState: null, + path: backingTrack.path, + name: backingTrack.name, + length: backingTrack.length ? backingTrack.length : 0 + } + console.log("options: ", options) + var $tr = $(context._.template($templateOpenBackingTrackRow.html(), options, { variable: 'data' })); + console.log("foo") + $tr.data('server-model', backingTrack); + console.log("bar") + console.log("Appending TR:") + $tbody.append($tr); + }); + }) + .fail(function(jqXHR, textStatus, errorMessage) { + app.ajaxError(jqXHR, textStatus, errorMessage); + }); + } + + + function registerStaticEvents() { + $tbody.on('click', 'tr', function(e) { + var backingTrack = $(this).data('server-model'); + + // tell the server we are about to open a backing track: + rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.path}) + .done(function(response) { + // TODO: Client stuff available?: + // context.jamClient.BackingTrackStopPlay(); + // var result = context.jamClient.BackingTrackPlay('t'); + + // logger.debug("BackingTrackPlay response: %o", result); + + // if(result) { + // app.layout.closeDialog('open-backing-track-dialog'); + // } + // else { + // logger.error("unable to open backing track") + // } + + }) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to Open BackingTrack For Playback"); + }) + + return false; + }) + + context.JK.helpBubble($downloadedTrackHelp, 'downloaded-backingtrack', {}, {width:'400px'}) + $downloadedTrackHelp.on('click', false) + + context.JK.helpBubble($whatAreBackingTracks, 'no help yet for this topic', {}, {positions:['bottom'], offsetParent: $dialog}) + $whatAreBackingTracks.on('click', false) // no help yet + } + + function initialize(){ + var dialogBindings = { + 'beforeShow' : beforeShow, + 'afterHide': afterHide + }; + + app.bindDialog('open-backing-track-dialog', dialogBindings); + + $dialog = $('#open-backing-track-dialog'); + $tbody = $dialog.find('table.open-backing-tracks tbody'); + $paginatorHolder = $dialog.find('.paginator-holder'); + $templateOpenBackingTrackRow = $('#template-backing-track-row') + $downloadedTrackHelp = $dialog.find('.downloaded-backingtrack-help') + $whatAreBackingTracks = $dialog.find('.what-are-backingtracks') + + registerStaticEvents(); + }; + + + this.initialize = initialize; + this.isShowing = function isShowing() { return showing; } + } + + return this; +})(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 29b5fe284..316eb31aa 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1157,6 +1157,32 @@ }) } + function openBackingTrack(options) { + var musicSessionId = options["id"]; + delete options["id"]; + + return $.ajax({ + type: "POST", + dataType: "json", + contentType: 'application/json', + url: "/api/sessions/" + musicSessionId + "/backing_tracks/open", + data: JSON.stringify(options) + }) + } + + function closeBackingTrack(options) { + var musicSessionId = options["id"]; + delete options["id"]; + + return $.ajax({ + type: "POST", + dataType: "json", + contentType: 'application/json', + url: "/api/sessions/" + musicSessionId + "/backing_tracks/close", + data: JSON.stringify(options) + }) + } + function openJamTrack(options) { var musicSessionId = options["id"]; var jamTrackId = options["jam_track_id"]; @@ -1367,6 +1393,15 @@ }); } + function getBackingTracks(options) { + return $.ajax({ + type: "GET", + url: '/api/backing_tracks?' + $.param(options), + dataType: "json", + contentType: 'application/json' + }); + } + function addJamtrackToShoppingCart(options) { return $.ajax({ type: "POST", @@ -1564,6 +1599,7 @@ this.startPlayClaimedRecording = startPlayClaimedRecording; this.stopPlayClaimedRecording = stopPlayClaimedRecording; this.openJamTrack = openJamTrack; + this.openBackingTrack = openBackingTrack; this.closeJamTrack = closeJamTrack; this.discardRecording = discardRecording; this.putTrackSyncChange = putTrackSyncChange; @@ -1595,6 +1631,7 @@ this.updateAudioLatency = updateAudioLatency; this.getJamtracks = getJamtracks; this.getPurchasedJamTracks = getPurchasedJamTracks; + this.getBackingTracks = getBackingTracks; this.addJamtrackToShoppingCart = addJamtrackToShoppingCart; this.getShoppingCarts = getShoppingCarts; this.removeShoppingCart = removeShoppingCart; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 84c390776..ccf6f9083 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -880,7 +880,96 @@ function renderBackingTracks(backingTrackMixers) { - logger.error("do not know how to draw backing tracks yet") + log.debug("rendering backing tracks") + var backingTracksPath = sessionModel.backingTrack(); + + // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) + // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener + var isOpener = backingTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup; + + // using the server's info in conjuction with the client's, draw the recording tracks + if(backingTracksPath) { + $('.session-recording-name').text(sessionModel.getCurrentSession().backing_track_path); + + var noCorrespondingTracks = false; + $.each(backingTrackMixers, function(index, mixer) { + var preMasteredClass = ""; + // find the track or tracks that correspond to the mixer + var correspondingTracks = [] + console.log("mixer", mixer) + if(mixer.id.indexOf("L") == 0) { + if(mixer.id.substring(1) == backingTrack.id) { + correspondingTracks.push(backingTrack); + } else { + // this should not be possible + alert("Invalid state: the recorded track had neither persisted_track_id or persisted_client_id"); + } + } + + if(correspondingTracks.length == 0) { + noCorrespondingTracks = true; + app.notify({ + title: "Unable to Open BackingTrack", + text: "Could not correlate server and client tracks", + icon_url: "/assets/content/icon_alert_big.png"}); + return false; + } + + // prune found recorded tracks + backingTracks = $.grep(backingTracks, function(value) { + return $.inArray(value, correspondingTracks) < 0; + }); + + var oneOfTheTracks = correspondingTracks[0]; + var instrumentIcon = context.JK.getInstrumentIcon45(oneOfTheTracks.instrument_id); + var photoUrl = "/assets/content/icon_recording.png"; + + var name = oneOfTheTracks.part + if (!name) { + name = oneOfTheTracks.instrument; + } + + // Default trackData to participant + no Mixer state. + var trackData = { + trackId: oneOfTheTracks.id, + clientId: oneOfTheTracks.client_id, + name: name, + instrumentIcon: instrumentIcon, + avatar: photoUrl, + latency: "good", + gainPercent: 0, + muteClass: 'muted', + mixerId: "", + avatarClass : 'avatar-recording', + preMasteredClass: "" + }; + + var gainPercent = percentFromMixerValue( + mixer.range_low, mixer.range_high, mixer.volume_left); + var muteClass = "enabled"; + if (mixer.mute) { + muteClass = "muted"; + } + trackData.gainPercent = gainPercent; + trackData.muteClass = muteClass; + trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) + trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) + trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) + + if(sessionModel.isPersonalMixMode() || !isOpener) { + trackData.mediaControlsDisabled = true; + trackData.mediaTrackOpener = isOpener; + } + _addRecordingTrack(trackData); + }); + + if(!noCorrespondingTracks && backingTracks.length > 0) { + logger.error("unable to find all backing tracks against client tracks"); + app.notify({title:"All tracks not found", + text: "Some tracks in the backing tracks are not present in the playback", + icon_url: "/assets/content/icon_alert_big.png"}) + } + } } function renderJamTracks(jamTrackMixers) { @@ -908,7 +997,7 @@ } else { // this should not be possible - alert("Invalid state: the recorded track had neither persisted_track_id or persisted_client_id"); + alert("Invalid state: the backing track had neither persisted_track_id or persisted_client_id"); } } }); @@ -1894,6 +1983,22 @@ .fail(app.ajaxError); } + function openBackingTrack(e) { + // just ignore the click if they are currently recording for now + if(sessionModel.recordingModel.isRecording()) { + app.notify({ + "title": "Currently Recording", + "text": "You can't open a backing track while creating a recording.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + return false; + } + + app.layout.showDialog('open-backing-track-dialog'); + + return false; + } + function openJamTrack(e) { // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { @@ -1910,6 +2015,23 @@ return false; } + function openMetronome(e) { + // just ignore the click if they are currently recording for now + if(sessionModel.recordingModel.isRecording()) { + app.notify({ + "title": "Currently Recording", + "text": "You can't open a metronome while creating a recording.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + return false; + } + + // TODO: + // Start metronome: + + return false; + } + function openRecording(e) { // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { @@ -1940,6 +2062,26 @@ } } + function closeBackingTrack() { + rest.closeBackingTrack({id: sessionModel.id()}) + .done(function() { + sessionModel.refreshCurrentSession(); + }) + .fail(function(jqXHR) { + app.notify({ + "title": "Couldn't Close BackingTrack", + "text": "Couldn't inform the server to close BackingTrack. msg=" + jqXHR.responseText, + "icon_url": "/assets/content/icon_alert_big.png" + }); + }); + + context.jamClient.closeBackingTrackFile(); + + return false; + } + + + function closeJamTrack() { rest.closeJamTrack({id: sessionModel.id()}) .done(function() { @@ -1958,6 +2100,8 @@ return false; } + + function closeRecording() { rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id}) .done(function() { @@ -2037,6 +2181,8 @@ $('#recording-start-stop').on('click', startStopRecording); $('#open-a-recording').on('click', openRecording); $('#open-a-jamtrack').on('click', openJamTrack); + $('#open-a-backingtrack').on('click', openBackingTrack); + $('#open-a-metronome').on('click', openMetronome); $('#session-invite-musicians').on('click', inviteMusicians); $('#session-invite-musicians2').on('click', inviteMusicians); $('#track-settings').click(function() { diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index d46319c45..c04156c3e 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -87,18 +87,18 @@ } } - function backingTracks() { + function backingTrack() { if(currentSession) { - return currentSession.backing_tracks + return currentSession.backing_track_path } else { return null; } } - function metronomeTracks() { + function metronomeActive() { if(currentSession) { - return currentSession.metronome_tracks + return currentSession.metronome_active } else { return null; diff --git a/web/app/assets/stylesheets/client/sessionList.css.scss b/web/app/assets/stylesheets/client/sessionList.css.scss index 16d208a59..b0df3def5 100644 --- a/web/app/assets/stylesheets/client/sessionList.css.scss +++ b/web/app/assets/stylesheets/client/sessionList.css.scss @@ -1,7 +1,7 @@ @import "client/common"; -table.findsession-table, table.local-recordings, table.open-jam-tracks, #account-session-detail { +table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks, #account-session-detail { .latency-unacceptable { width: 50px; @@ -64,7 +64,7 @@ table.findsession-table, table.local-recordings, table.open-jam-tracks, #account text-align:center; } } -table.findsession-table, table.local-recordings, table.open-jam-tracks { +table.findsession-table, table.local-recordings, table.open-jam-tracks, table.open-backing-tracks { width:98%; height:10%; font-size:11px; diff --git a/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss b/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss new file mode 100644 index 000000000..a4770e627 --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss @@ -0,0 +1,44 @@ +@import "client/common"; + +#open-backing-track-dialog { + table.open-backing-tracks { + tbody { + tr:hover { + background-color: #777; + cursor:pointer; + } + + tr[data-local-state=MISSING], tr[data-local-state=PARTIALLY_MISSING] { + background-color:#777; + color:#aaa; + } + } + } + + .downloaded-backingtrack-help { + margin-left:15px; + } + + .right { + margin-right:10px; + } + + .help-links { + text-align: center; + position: absolute; + margin: 0 auto; + width: 70%; + left: 15%; + font-size: 12px; + padding-top:5px; + + a { + margin:0 10px; + } + } + + .paginator-holder { + padding-top:3px; + } +} + diff --git a/web/app/controllers/api_backing_tracks_controller.rb b/web/app/controllers/api_backing_tracks_controller.rb new file mode 100644 index 000000000..618937052 --- /dev/null +++ b/web/app/controllers/api_backing_tracks_controller.rb @@ -0,0 +1,17 @@ +class ApiBackingTracksController < ApiController + + # have to be signed in currently to see this screen + before_filter :api_signed_in_user + + respond_to :json + + def index + tracks = [ + {:name=>'foo',:path=>"foobar.mp3", :length=>4283}, + {:name=>'bar',:path=>"foo.mp3",:length=>3257} + ] + @backing_tracks, @next = tracks, nil + render "api_backing_tracks/index", :layout => nil + end + +end # class ApiJamTracksController diff --git a/web/app/views/api_backing_tracks/index.rabl b/web/app/views/api_backing_tracks/index.rabl new file mode 100644 index 000000000..a5b9b4b9b --- /dev/null +++ b/web/app/views/api_backing_tracks/index.rabl @@ -0,0 +1,7 @@ +node :next do |page| + @next +end + +node :backing_tracks do |page| + @backing_tracks +end diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 5f51e17ed..3f11423fe 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -119,7 +119,7 @@ <% if Rails.application.config.jam_tracks_available %>
  • JamTrack
  • <% end %> -
  • Audio File
  • +
  • Audio File

  • diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 92d50feda..94af76504 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -155,6 +155,9 @@ var openJamTrackDialog = new JK.OpenJamTrackDialog(JK.app); openJamTrackDialog.initialize(); + var openBackingTrackDialog = new JK.OpenBackingTrackDialog(JK.app); + openBackingTrackDialog.initialize(); + var configureTracksDialog = new JK.ConfigureTracksDialog(JK.app); configureTracksDialog.initialize(); diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 56ec88ccc..2b7ac1206 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -33,3 +33,4 @@ = render 'dialogs/allSyncsDialog' = render 'dialogs/adjustGearSpeedDialog' = render 'dialogs/openJamTrackDialog' += render 'dialogs/openBackingTrackDialog' diff --git a/web/app/views/dialogs/_openBackingTrackDialog.html.slim b/web/app/views/dialogs/_openBackingTrackDialog.html.slim index 18ca215a7..3c2d422e8 100644 --- a/web/app/views/dialogs/_openBackingTrackDialog.html.slim +++ b/web/app/views/dialogs/_openBackingTrackDialog.html.slim @@ -1,50 +1,48 @@ -.dialog.openJamTrackDialog-overlay.ftue-overlay.tall#open-jam-track-dialog layout="dialog" layout-id="open-jam-track-dialog" +.dialog.openBackingTrackDialog-overlay.ftue-overlay.tall#open-backing-track-dialog layout="dialog" layout-id="open-backing-track-dialog" .content-head = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' } h1 - | open a jamtrack + | open an audio file .dialog-inner .recording-wrapper - table.open-jam-tracks cellspacing="0" cellpadding="0" border="0" + table.open-backing-tracks cellspacing="0" cellpadding="0" border="0" thead tr th align="left" | NAME th align="left" - | ORIGINAL ARTIST + | PATH th align="left" - | DOWNLOADED - a.downloaded-jamtrack-help href="#" - | ? + | LENGTH + / th align="left" + / | ORIGINAL ARTIST + / th align="left" + / | DOWNLOADED + / a.downloaded-backingtrack-help href="#" + / | ? tbody - br .left.paginator-holder .help-links - a.what-are-jamtracks href='#' - | What are JamTracks? - a href='/client#/jamtrack' rel="external" - | Shop for JamTracks + a.what-are-backingtracks href='#' + | What are Backing Tracks? .right a href="#" class="button-grey" layout-action="close" | CANCEL - - br clear="all" - - script#template-jam-track-row type="text/template" - tr data-recording-id="{{data.jamTrackId}}" data-local-state="{{data.jamTrackState}}" + script#template-backing-track-row type="text/template" + tr data-recording-id="{{data.backingTrackId}}" data-local-state="{{data.backingTrackState}}" td | {{data.name}} td - | {{data.artist}} + | {{data.path}} td - | {{data.downloaded}} + | {{data.length}} diff --git a/web/app/views/dialogs/_openMetronomeDialog.html.slim b/web/app/views/dialogs/_openMetronomeDialog.html.slim deleted file mode 100644 index 18ca215a7..000000000 --- a/web/app/views/dialogs/_openMetronomeDialog.html.slim +++ /dev/null @@ -1,50 +0,0 @@ -.dialog.openJamTrackDialog-overlay.ftue-overlay.tall#open-jam-track-dialog layout="dialog" layout-id="open-jam-track-dialog" - - .content-head - = image_tag "content/icon_add.png", {:width => 19, :height => 19, :class => 'content-icon' } - h1 - | open a jamtrack - - .dialog-inner - - .recording-wrapper - table.open-jam-tracks cellspacing="0" cellpadding="0" border="0" - thead - tr - th align="left" - | NAME - th align="left" - | ORIGINAL ARTIST - th align="left" - | DOWNLOADED - a.downloaded-jamtrack-help href="#" - | ? - tbody - - br - - .left.paginator-holder - - .help-links - a.what-are-jamtracks href='#' - | What are JamTracks? - a href='/client#/jamtrack' rel="external" - | Shop for JamTracks - .right - a href="#" class="button-grey" layout-action="close" - | CANCEL - - - br clear="all" - - - script#template-jam-track-row type="text/template" - tr data-recording-id="{{data.jamTrackId}}" data-local-state="{{data.jamTrackState}}" - td - | {{data.name}} - td - | {{data.artist}} - td - | {{data.downloaded}} - - diff --git a/web/config/routes.rb b/web/config/routes.rb index befe50ffb..eb142fd0e 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -178,7 +178,7 @@ SampleApp::Application.routes.draw do match '/sessions/:id/details/comments' => 'api_music_sessions#add_session_info_comment', :via => :post match '/sessions/:id/jam_tracks/:jam_track_id/open' => 'api_music_sessions#jam_track_open', :via => :post match '/sessions/:id/jam_tracks/close' => 'api_music_sessions#jam_track_close', :via => :post - match '/sessions/:id/backing_tracks/:backing_track_id/open' => 'api_music_sessions#backing_track_open', :via => :post + match '/sessions/:id/backing_tracks/open' => 'api_music_sessions#backing_track_open', :via => :post match '/sessions/:id/backing_tracks/close' => 'api_music_sessions#backing_track_close', :via => :post match '/sessions/:id/jam_tracks/:metronome_id/open' => 'api_music_sessions#metronome_open', :via => :post match '/sessions/:id/metronomes/close' => 'api_music_sessions#metronome_close', :via => :post @@ -195,6 +195,9 @@ SampleApp::Application.routes.draw do match '/music_notations' => 'api_music_notations#create', :via => :post match '/music_notations/:id' => 'api_music_notations#download', :via => :get, :as => :download_music_notation + # Backing track_show + match '/backing_tracks' => 'api_backing_tracks#index', :via => :get, :as => 'api_backing_tracks_list' + # Jamtracks match '/jamtracks' => 'api_jam_tracks#index', :via => :get, :as => 'api_jam_tracks_list' match '/jamtracks/purchased' => 'api_jam_tracks#purchased', :via => :get, :as => 'api_jam_tracks_purchased' From 5f9f8a0cddb47b7af06bee03baee91abe1ea0aa4 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 16 Jan 2015 18:00:44 -0600 Subject: [PATCH 09/46] Cleanup; upgrade deprecated gem. Attempt to fix intermittently failing test. --- web/Gemfile | 2 +- web/app/assets/javascripts/dialog/openBackingTrackDialog.js | 4 ---- .../stylesheets/dialogs/openBackingTrackDialog.css.scss | 4 ---- web/app/views/dialogs/_openBackingTrackDialog.html.slim | 6 ------ web/spec/features/find_sessions_spec.rb | 6 +++++- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/web/Gemfile b/web/Gemfile index e5c3f1583..ebd231f77 100644 --- a/web/Gemfile +++ b/web/Gemfile @@ -75,7 +75,7 @@ gem 'netaddr' gem 'quiet_assets', :group => :development gem 'bugsnag' gem 'multi_json', '1.9.0' -gem 'rest_client' +gem 'rest-client' gem 'iso-639' gem 'language_list' gem 'rubyzip' diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index 905d6d9e9..2bb5f796f 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -103,9 +103,6 @@ return false; }) - context.JK.helpBubble($downloadedTrackHelp, 'downloaded-backingtrack', {}, {width:'400px'}) - $downloadedTrackHelp.on('click', false) - context.JK.helpBubble($whatAreBackingTracks, 'no help yet for this topic', {}, {positions:['bottom'], offsetParent: $dialog}) $whatAreBackingTracks.on('click', false) // no help yet } @@ -122,7 +119,6 @@ $tbody = $dialog.find('table.open-backing-tracks tbody'); $paginatorHolder = $dialog.find('.paginator-holder'); $templateOpenBackingTrackRow = $('#template-backing-track-row') - $downloadedTrackHelp = $dialog.find('.downloaded-backingtrack-help') $whatAreBackingTracks = $dialog.find('.what-are-backingtracks') registerStaticEvents(); diff --git a/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss b/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss index a4770e627..830d5c61c 100644 --- a/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss @@ -15,10 +15,6 @@ } } - .downloaded-backingtrack-help { - margin-left:15px; - } - .right { margin-right:10px; } diff --git a/web/app/views/dialogs/_openBackingTrackDialog.html.slim b/web/app/views/dialogs/_openBackingTrackDialog.html.slim index 3c2d422e8..dec11d179 100644 --- a/web/app/views/dialogs/_openBackingTrackDialog.html.slim +++ b/web/app/views/dialogs/_openBackingTrackDialog.html.slim @@ -17,12 +17,6 @@ | PATH th align="left" | LENGTH - / th align="left" - / | ORIGINAL ARTIST - / th align="left" - / | DOWNLOADED - / a.downloaded-backingtrack-help href="#" - / | ? tbody br diff --git a/web/spec/features/find_sessions_spec.rb b/web/spec/features/find_sessions_spec.rb index 5853c13ce..cb6d15ba2 100644 --- a/web/spec/features/find_sessions_spec.rb +++ b/web/spec/features/find_sessions_spec.rb @@ -9,6 +9,10 @@ describe "Find Session", :js => true, :type => :feature, :capybara_feature => tr let(:dallas) { dallas_geoip } let(:user) { FactoryGirl.create(:user, last_jam_locidispid: austin_geoip[:locidispid], last_jam_addr: austin_ip) } let(:finder) { FactoryGirl.create(:user, last_jam_locidispid: dallas_geoip[:locidispid], last_jam_addr: dallas_ip) } + + before(:all) do + Capybara.default_wait_time = 20 + end before(:each) do @@ -82,7 +86,7 @@ describe "Find Session", :js => true, :type => :feature, :capybara_feature => tr # this should cause it to move to the scheduled session view; we'll check all the values again ActiveMusicSession.delete_all - + sleep(3) verify_find_session_score(nil, '#sessions-scheduled', session1_creator, session1_creator) fast_signout From ff95fe1c35ad5f5a660020ec5961970a15cc8e7e Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 20 Jan 2015 22:14:48 -0600 Subject: [PATCH 10/46] VRFS-2498, VRFS-2499 : Mesh out backing track/metronome track support at necessary layers rest/api/UI/etc. Implement necessary methods in fakeJamClient for quicker iterating. --- .../images/content/icon_metronome_small.png | Bin 0 -> 6433 bytes .../dialog/openBackingTrackDialog.js | 14 +- web/app/assets/javascripts/fakeJamClient.js | 77 ++++- web/app/assets/javascripts/jam_rest.js | 34 +- web/app/assets/javascripts/session.js | 320 ++++++++++++------ web/app/assets/javascripts/sessionModel.js | 7 +- web/app/views/api_music_sessions/show.rabl | 2 +- web/app/views/clients/_session.html.erb | 2 +- web/config/routes.rb | 4 +- .../lib/jam_websockets/router.rb | 4 + 10 files changed, 344 insertions(+), 120 deletions(-) create mode 100644 web/app/assets/images/content/icon_metronome_small.png diff --git a/web/app/assets/images/content/icon_metronome_small.png b/web/app/assets/images/content/icon_metronome_small.png new file mode 100644 index 0000000000000000000000000000000000000000..b24f11fdbd3fcadea6100ac43b1f7e6c643e4b9e GIT binary patch literal 6433 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 qY<9iL;#D*?&yp{4XoU}SL9CgSoRY^|-1F 0) { - renderMetronomeTracks(jamTrackMixers); + renderMetronomeTracks(metronomeTrackMixers); } if(adhocTrackMixers.length > 0) { logger.warn("some tracks are open that we don't know how to show") @@ -880,100 +884,86 @@ function renderBackingTracks(backingTrackMixers) { - log.debug("rendering backing tracks") - var backingTracksPath = sessionModel.backingTrack(); - + var backingTrack = sessionModel.backingTrack() + var backingTrackPath = backingTrack ? backingTrack.path : null + console.log("rendering backing track",backingTrackPath) + // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener var isOpener = backingTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup; + // using the server's info in conjuction with the client's, draw the recording tracks - if(backingTracksPath) { - $('.session-recording-name').text(sessionModel.getCurrentSession().backing_track_path); + if(backingTrackPath && backingTrackMixers.length > 0) { + var backingTrack = {path: backingTrackPath} + var name = sessionModel.getCurrentSession().backing_track_path + + $('.session-recording-name').text(name); var noCorrespondingTracks = false; - $.each(backingTrackMixers, function(index, mixer) { - var preMasteredClass = ""; - // find the track or tracks that correspond to the mixer - var correspondingTracks = [] - console.log("mixer", mixer) - if(mixer.id.indexOf("L") == 0) { - if(mixer.id.substring(1) == backingTrack.id) { - correspondingTracks.push(backingTrack); - } else { - // this should not be possible - alert("Invalid state: the recorded track had neither persisted_track_id or persisted_client_id"); - } - } - - if(correspondingTracks.length == 0) { - noCorrespondingTracks = true; - app.notify({ - title: "Unable to Open BackingTrack", - text: "Could not correlate server and client tracks", - icon_url: "/assets/content/icon_alert_big.png"}); - return false; - } - - // prune found recorded tracks - backingTracks = $.grep(backingTracks, function(value) { - return $.inArray(value, correspondingTracks) < 0; - }); - - var oneOfTheTracks = correspondingTracks[0]; - var instrumentIcon = context.JK.getInstrumentIcon45(oneOfTheTracks.instrument_id); - var photoUrl = "/assets/content/icon_recording.png"; - - var name = oneOfTheTracks.part - if (!name) { - name = oneOfTheTracks.instrument; - } - - // Default trackData to participant + no Mixer state. - var trackData = { - trackId: oneOfTheTracks.id, - clientId: oneOfTheTracks.client_id, - name: name, - instrumentIcon: instrumentIcon, - avatar: photoUrl, - latency: "good", - gainPercent: 0, - muteClass: 'muted', - mixerId: "", - avatarClass : 'avatar-recording', - preMasteredClass: "" - }; - - var gainPercent = percentFromMixerValue( - mixer.range_low, mixer.range_high, mixer.volume_left); - var muteClass = "enabled"; - if (mixer.mute) { - muteClass = "muted"; - } - trackData.gainPercent = gainPercent; - trackData.muteClass = muteClass; - trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) - trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) - trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) - - if(sessionModel.isPersonalMixMode() || !isOpener) { - trackData.mediaControlsDisabled = true; - trackData.mediaTrackOpener = isOpener; - } - _addRecordingTrack(trackData); - }); - - if(!noCorrespondingTracks && backingTracks.length > 0) { - logger.error("unable to find all backing tracks against client tracks"); - app.notify({title:"All tracks not found", - text: "Some tracks in the backing tracks are not present in the playback", - icon_url: "/assets/content/icon_alert_big.png"}) + var mixer = backingTrackMixers[0] + var preMasteredClass = ""; + // find the track or tracks that correspond to the mixer + var correspondingTracks = [] + console.log("mixer", mixer) + correspondingTracks.push(backingTrack); + + if(correspondingTracks.length == 0) { + noCorrespondingTracks = true; + app.notify({ + title: "Unable to Open BackingTrack", + text: "Could not correlate server and client tracks", + icon_url: "/assets/content/icon_alert_big.png"}); + return false; } - } + + // prune found recorded tracks + // backingTracks = $.grep(backingTracks, function(value) { + // return $.inArray(value, correspondingTracks) < 0; + // }); + + var oneOfTheTracks = correspondingTracks[0]; + var instrumentIcon = context.JK.getInstrumentIcon45(oneOfTheTracks.instrument_id); + var photoUrl = "/assets/content/icon_recording.png"; + + + // Default trackData to participant + no Mixer state. + var trackData = { + trackId: oneOfTheTracks.id, + clientId: oneOfTheTracks.client_id, + name: name, + instrumentIcon: instrumentIcon, + avatar: photoUrl, + latency: "good", + gainPercent: 0, + muteClass: 'muted', + mixerId: "", + avatarClass : 'avatar-recording', + preMasteredClass: "" + }; + + var gainPercent = percentFromMixerValue( + mixer.range_low, mixer.range_high, mixer.volume_left); + var muteClass = "enabled"; + if (mixer.mute) { + muteClass = "muted"; + } + trackData.gainPercent = gainPercent; + trackData.muteClass = muteClass; + trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) + trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) + trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) + + if(sessionModel.isPersonalMixMode() || !isOpener) { + trackData.mediaControlsDisabled = true; + trackData.mediaTrackOpener = isOpener; + } + _addRecordingTrack(trackData); + }// if } function renderJamTracks(jamTrackMixers) { - log.debug("rendering jam tracks") + console.log("rendering jam tracks") var jamTracks = sessionModel.jamTracks(); // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) @@ -1069,7 +1059,80 @@ } function renderMetronomeTracks(metronomeTrackMixers) { - logger.error("do not know how to draw metronome tracks yet") + var metronomeActive = sessionModel.metronomeActive(); + console.log("rendering metronome track",metronomeActive) + + // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) + // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener + var isOpener = metronomeTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup; + var name = "Metronome" + + // using the server's info in conjuction with the client's, draw the recording tracks + if(metronomeActive && metronomeTrackMixers.length > 0) { + console.log("Rendering active metronome."); + var metronome = {active: metronomeActive} + $('.session-recording-name').text(name);//sessionModel.getCurrentSession().backing_track_path); + + var noCorrespondingTracks = false; + var mixer = metronomeTrackMixers[0] + var preMasteredClass = ""; + // find the track or tracks that correspond to the mixer + var correspondingTracks = [] + console.log("mixer", mixer) + correspondingTracks.push(metronome); + + if(correspondingTracks.length == 0) { + noCorrespondingTracks = true; + app.notify({ + title: "Unable to Open Metronome", + text: "Could not correlate server and client tracks", + icon_url: "/assets/content/icon_metronome_small.png"}); + return false; + } + + // prune found recorded tracks + // Metronomes = $.grep(Metronomes, function(value) { + // return $.inArray(value, correspondingTracks) < 0; + // }); + + var oneOfTheTracks = correspondingTracks[0]; + var instrumentIcon = context.JK.getInstrumentIcon45(oneOfTheTracks.instrument_id); + var photoUrl = "/assets/content/icon_metronome_small.png"; + + + // Default trackData to participant + no Mixer state. + var trackData = { + trackId: oneOfTheTracks.id, + clientId: oneOfTheTracks.client_id, + name: name, + instrumentIcon: instrumentIcon, + avatar: photoUrl, + latency: "good", + gainPercent: 0, + muteClass: 'muted', + mixerId: "", + avatarClass : 'avatar-recording', + preMasteredClass: "" + }; + + var gainPercent = percentFromMixerValue( + mixer.range_low, mixer.range_high, mixer.volume_left); + var muteClass = "enabled"; + if (mixer.mute) { + muteClass = "muted"; + } + trackData.gainPercent = gainPercent; + trackData.muteClass = muteClass; + trackData.mixerId = mixer.id; // the master mixer controls the volume control for recordings (no personal controls in either master or personal mode) + trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) + trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) + + if(sessionModel.isPersonalMixMode() || !isOpener) { + trackData.mediaControlsDisabled = true; + trackData.mediaTrackOpener = isOpener; + } + _addRecordingTrack(trackData); + }// if } @@ -1882,6 +1945,7 @@ } function sessionResync(evt) { + console.log("Calling session resync...") evt.preventDefault(); var response = context.jamClient.SessionAudioResync(); if (response) { @@ -2031,23 +2095,53 @@ return false; } + function openBackingTrackFile(e) { + // just ignore the click if they are currently recording for now + console.log("opening backing track file") + if(sessionModel.recordingModel.isRecording()) { + app.notify({ + "title": "Currently Recording", + "text": "You can't open a backing track while creating a recording.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + return false; + } else { + context.jamClient.openBackingTrackFile(sessionModel.backing_track) + context.JK.CurrentSessionModel.refreshCurrentSession(true); + } + return false; + } + function openMetronome(e) { // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { - app.notify({ - "title": "Currently Recording", - "text": "You can't open a metronome while creating a recording.", - "icon_url": "/assets/content/icon_alert_big.png" - }); + app.notify({ + "title": "Currently Recording", + "text": "You can't open a metronome while creating a recording.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + return false; + } else { + rest.openMetronome({id: sessionModel.id()}) + .done(function() { + context.jamClient.SessionSetMetronome(120, "click"); + context.JK.CurrentSessionModel.refreshCurrentSession(true); + }) + .fail(function(jqXHR) { + console.log(jqXHR, jqXHR) + app.notify({ + "title": "Couldn't open metronome", + "text": "Couldn't inform open metronome. msg=" + jqXHR.responseText, + "icon_url": "/assets/content/icon_alert_big.png" + }); + }); + + return false; } - - // TODO: - // Start metronome: - - return false; } + function openRecording(e) { // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { @@ -2073,6 +2167,12 @@ else if(sessionModel.jamTracks()) { closeJamTrack(); } + else if(sessionModel.backingTrack() && sessionModel.backingTrack().path) { + closeBackingTrack(); + } + else if(sessionModel.metronomeActive()) { + closeMetronomeTrack(); + } else { logger.error("don't know how to close open media (backing track?)"); } @@ -2081,7 +2181,7 @@ function closeBackingTrack() { rest.closeBackingTrack({id: sessionModel.id()}) .done(function() { - sessionModel.refreshCurrentSession(); + sessionModel.refreshCurrentSession(true); }) .fail(function(jqXHR) { app.notify({ @@ -2094,14 +2194,12 @@ context.jamClient.closeBackingTrackFile(); return false; - } - - + } function closeJamTrack() { rest.closeJamTrack({id: sessionModel.id()}) .done(function() { - sessionModel.refreshCurrentSession(); + sessionModel.refreshCurrentSession(true); }) .fail(function(jqXHR) { app.notify({ @@ -2116,12 +2214,26 @@ return false; } - + function closeMetronomeTrack() { + rest.closeMetronome({id: sessionModel.id()}) + .done(function() { + context.jamClient.SessionCloseMetronome(); + sessionModel.refreshCurrentSession(true); + }) + .fail(function(jqXHR) { + app.notify({ + "title": "Couldn't Close MetronomeTrack", + "text": "Couldn't inform the server to close MetronomeTrack. msg=" + jqXHR.responseText, + "icon_url": "/assets/content/icon_alert_big.png" + }); + }); + return false; + } function closeRecording() { rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id}) .done(function() { - sessionModel.refreshCurrentSession(); + sessionModel.refreshCurrentSession(true); }) .fail(function(jqXHR) { app.notify({ diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index ee3d04087..890231ded 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -90,7 +90,10 @@ function backingTrack() { if(currentSession) { - return currentSession.backing_track_path + // TODO: objectize this for VRFS-2665, VRFS-2666, VRFS-2667, VRFS-2668 + return { + path: currentSession.backing_track_path + } } else { return null; @@ -747,6 +750,8 @@ // Public interface this.id = id; this.start = start; + this.backingTrack = backingTrack; + this.metronomeActive = metronomeActive; this.setUserTracks = setUserTracks; this.recordedTracks = recordedTracks; this.jamTracks = jamTracks; diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index 124f9ba56..1d87b55c1 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -13,7 +13,7 @@ if !current_user } else - attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score + attributes :id, :name, :description, :musician_access, :approval_required, :fan_access, :fan_chat, :band_id, :user_id, :claimed_recording_initiator_id, :track_changes_counter, :max_score, :backing_track_path, :metronome_active node :can_join do |session| session.can_join?(current_user, true) diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb index 3f11423fe..c5b71750c 100644 --- a/web/app/views/clients/_session.html.erb +++ b/web/app/views/clients/_session.html.erb @@ -125,7 +125,7 @@
    <%= image_tag "content/icon_metronome.png", {width:22, height:20} %> - Use Metronome + Use Metronome
    diff --git a/web/config/routes.rb b/web/config/routes.rb index eb142fd0e..e1326b4a4 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -180,8 +180,8 @@ SampleApp::Application.routes.draw do match '/sessions/:id/jam_tracks/close' => 'api_music_sessions#jam_track_close', :via => :post match '/sessions/:id/backing_tracks/open' => 'api_music_sessions#backing_track_open', :via => :post match '/sessions/:id/backing_tracks/close' => 'api_music_sessions#backing_track_close', :via => :post - match '/sessions/:id/jam_tracks/:metronome_id/open' => 'api_music_sessions#metronome_open', :via => :post - match '/sessions/:id/metronomes/close' => 'api_music_sessions#metronome_close', :via => :post + match '/sessions/:id/metronome/open' => 'api_music_sessions#metronome_open', :via => :post + match '/sessions/:id/metronome/close' => 'api_music_sessions#metronome_close', :via => :post # music session tracks match '/sessions/:id/tracks' => 'api_music_sessions#track_create', :via => :post diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb index cf00b389b..6e58ebb9a 100644 --- a/websocket-gateway/lib/jam_websockets/router.rb +++ b/websocket-gateway/lib/jam_websockets/router.rb @@ -706,6 +706,10 @@ module JamWebsockets unless music_session_upon_reentry.nil? || music_session_upon_reentry.destroyed? + if music_session_upon_reentry.backing_track_initiator == user + music_session_upon_reentry.close_backing_track + end + # if a jamtrack is open and this user is no longer in the session, close it if music_session_upon_reentry.jam_track_initiator == user music_session_upon_reentry.close_jam_track From 4741312294f8dc2398c2f203b98101c0135ba2cc Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 21 Jan 2015 15:10:02 -0600 Subject: [PATCH 11/46] Fix test, cleanup --- ruby/spec/jam_ruby/models/active_music_session_spec.rb | 1 - web/app/assets/javascripts/fakeJamClient.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ruby/spec/jam_ruby/models/active_music_session_spec.rb b/ruby/spec/jam_ruby/models/active_music_session_spec.rb index ec160d0bb..07d6f49a5 100644 --- a/ruby/spec/jam_ruby/models/active_music_session_spec.rb +++ b/ruby/spec/jam_ruby/models/active_music_session_spec.rb @@ -938,7 +938,6 @@ describe ActiveMusicSession do it "allow a metronome to be activated" do # simple success case; just open the metronome and observe the state of the session is correct @music_session.open_metronome(@user1) - puts "@music_session.errors: #{@music_session.errors.inspect}" @music_session.errors.any?.should be_false @music_session.reload @music_session.metronome_active.should == true diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index e91e314bf..993f1fec6 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -404,7 +404,7 @@ } function SessionGetControlState(mixerIds, isMasterOrPersonal) { dbg("SessionGetControlState"); - var groups = [0, 1, 2, 2, 3, 7, 8, 10, 11, 12]; + var groups = [0, 1, 2, 3, 3, 7, 8, 10, 11, 12]; var names = [ "FW AP Multi", "FW AP Multi", From 0d9bd33eb7e5488293146cac6cad8e2185a92529 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 21 Jan 2015 20:16:52 -0600 Subject: [PATCH 12/46] Clean up and adjust session UI to display metronome tempos and click sound. Update client. Style updates to fit selects and default to 120 bpm. --- web/app/assets/javascripts/fakeJamClient.js | 5 +- web/app/assets/javascripts/session.js | 127 +++++++++++++----- .../stylesheets/client/session.css.scss | 46 ++++++- web/app/helpers/sessions_helper.rb | 6 + web/app/views/clients/_session.html.erb | 18 ++- 5 files changed, 160 insertions(+), 42 deletions(-) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 993f1fec6..1a42a27b5 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -736,6 +736,7 @@ } function SessionOpenMetronome(bpm, click, meter, mode){ + console.log("Setting metronome BPM: ", bpm) metronomeActive =true metronomeBPM = bpm metronomeSound = click @@ -743,8 +744,8 @@ } //change setting - click. Mode 0: = mono, 1, = left ear, 2= right ear - function SessionSetMetronome(bmp, click,meter, mode){ - SessionOpenMetronome(bmp, click, meter, mode) + function SessionSetMetronome(bpm,click,meter, mode){ + SessionOpenMetronome(bpm, click, meter, mode) } //close everywhere diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 69dff73ac..697cfd4d6 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -4,6 +4,7 @@ context.JK = context.JK || {}; context.JK.SessionScreen = function(app) { + var TEMPOS = context.JK.TEMPOS; var EVENTS = context.JK.EVENTS; var MIX_MODES = context.JK.MIX_MODES; var NAMED_MESSAGES = context.JK.NAMED_MESSAGES; @@ -500,7 +501,6 @@ } function renderSession() { - console.log("RENDERING SESSION") $('#session-mytracks-container').empty(); $('.session-track').remove(); // Remove previous tracks var $voiceChat = $('#voice-chat'); @@ -676,7 +676,6 @@ //logger.debug("clientId", clientId, "groupIds", groupIds, "mixers", mixers) var foundMixers = {}; var mixers = mixMode == MIX_MODES.MASTER ? masterMixers : personalMixers; - // console.log("_groupedMixersForClientId", mixers) $.each(mixers, function(index, mixer) { if (mixer.client_id === clientId) { for (var i=0; iAudio File -
    <%= image_tag "content/icon_metronome.png", {width:22, height:20} %> Use Metronome @@ -192,7 +191,22 @@
    CONNECTION
    -
    +
    + From 23d4c88f46cffb6d568f7cab5b6aace21ea49b6a Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Mon, 26 Jan 2015 14:24:15 -0600 Subject: [PATCH 13/46] Convert session partial to slim. --- web/app/views/clients/_session.html.erb | 221 ----------------------- web/app/views/clients/_session.html.slim | 132 ++++++++++++++ 2 files changed, 132 insertions(+), 221 deletions(-) delete mode 100644 web/app/views/clients/_session.html.erb create mode 100644 web/app/views/clients/_session.html.slim diff --git a/web/app/views/clients/_session.html.erb b/web/app/views/clients/_session.html.erb deleted file mode 100644 index a8ee73710..000000000 --- a/web/app/views/clients/_session.html.erb +++ /dev/null @@ -1,221 +0,0 @@ - -
    -
    -
    - <%= image_tag "shared/icon_session.png", {:height => 19, :width => 19} %> -
    -

    session

    -
    - -
    - - - - - -
    -
    - - -
    - - -
    -

    my tracks

    -
    - <%= image_tag "content/icon_settings_lg.png", {:width => 18, :height => 18} %> - Settings -
    - -
    -
    - -
    -
    - - - - - -
    -

    other audio

    - -
    -
    -
    - <%= image_tag "content/icon_folder.png", {width:22, height:20} %> Open: - -
    -
    - <%= image_tag "content/icon_metronome.png", {width:22, height:20} %> - Use Metronome - -
    - -
    -
    - - - <%= render "play_controls" %> - -
    - - - -
    - -
    -
    -
    -
    -
    - - -<%= render "configureTrack" %> -<%= render "addTrack" %> -<%= render "addNewGear" %> -<%= render "error" %> -<%= render "sessionSettings" %> - - - - - - - - - diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim new file mode 100644 index 000000000..d5cd7fac3 --- /dev/null +++ b/web/app/views/clients/_session.html.slim @@ -0,0 +1,132 @@ +#session-screen.screen.secondary[layout="screen" layout-id="session" layout-arg="id"] + .content-head + .content-icon + = image_tag "shared/icon_session.png", {:height => 19, :width => 19} + h1 + | session + .content-body + #session-controls + a#session-resync.button-grey.resync.left + = image_tag "content/icon_resync.png", {:align => "texttop", :height => 14, :width => 12} + | RESYNC + a#session-settings-button.button-grey.left[layout-link="session-settings"] + = image_tag "content/icon_settings_sm.png", {:align => "texttop", :height => 12, :width => 12} + | SETTINGS + a.button-grey.left[layout-link="share-dialog"] + = image_tag "content/icon_share.png", {:align => "texttop", :height => 12, :width => 12} + | SHARE + .block + .label + | VOLUME: + #volume.fader.lohi[mixer-id=""] + .block.monitor-mode-holder + .label + | MIX: + select.monitor-mode.easydropdown + option.label[value="personal"] + | Personal + option[value="master"] + | Master + a#session-leave.button-grey.right.leave[href="/client#/home"] + | X  LEAVE + #tracks + .content-scroller + .content-wrapper + .session-mytracks + h2 + | my tracks + #track-settings.session-add[style="display:block;" layout-link="configure-tracks"] + = image_tag "content/icon_settings_lg.png", {:width => 18, :height => 18} + span + | Settings + .session-tracks-scroller + #session-mytracks-container + #voice-chat.voicechat[style="display:none;" mixer-id=""] + .voicechat-label + | CHAT + .voicechat-gain + .voicechat-mute.enabled[control="mute" mixer-id=""] + .session-livetracks + h2 + | live tracks + .session-add[layout-link="select-invites"] + a#session-invite-musicians[href="#"] + = image_tag "content/icon_add.png", {:width => 19, :height => 19, :align => "texttop"} + |   Invite Musicians + .session-tracks-scroller + #session-livetracks-container + .when-empty.livetracks + | No other musicians + br + | are in your session + br[clear="all"] + #recording-start-stop.recording + a + = image_tag "content/recordbutton-off.png", {:width => 20, :height => 20, :align => "absmiddle"} + |    + span#recording-status + | Make a Recording + .session-recordings + h2 + | other audio + .session-recording-name-wrapper + .session-recording-name.left + | (No recording loaded) + .session-add.right + a#close-playback-recording[href="#"] + = image_tag "content/icon_close.png", {:width => 18, :height => 20, :align => "texttop"} + |   Close + .session-tracks-scroller + #session-recordedtracks-container + .when-empty.recordings + span.open-media-file-header + = image_tag "content/icon_folder.png", {width:22, height:20} + | Open: + ul.open-media-file-options + li + a#open-a-recording[href="#"] + | Recording + - if Rails.application.config.jam_tracks_available + li + a#open-a-jamtrack[href="#"] + | JamTrack + li + a#open-a-backingtrack[href="#"] + | Audio File + .when-empty.use-metronome-header + = image_tag "content/icon_metronome.png", {width:22, height:20} + a#open-a-metronome[href="#"] + | Use Metronome + br[clear="all"] + = render "play_controls" += render "configureTrack" += render "addTrack" += render "addNewGear" += render "error" += render "sessionSettings" +script#template-session-track[type="text/template"] + .session-track.track client-id="{clientId}" track-id="{trackId}" + .track-vu-left.mixer-id="{vuMixerId}_vul" + .track-vu-right.mixer-id="{vuMixerId}_vur" + .track-label="{name}" + #div-track-close.track-close.op30 track-id="{trackId}" + =image_tag("content/icon_closetrack.png", {width: 12, height: 12}) + div class="{avatarClass}" + img src="{avatar}" + .track-instrument class="{preMasteredClass}" + img height="45" src="{instrumentIcon}" width="45" + .track-gain mixer-id="{mixerId}" + .track-icon-mute class="{muteClass}" control="mute" mixer-id="{muteMixerId}" + .track-connection.grey mixer-id="{mixerId}_connection" + CONNECTION + .disabled-track-overlay + .metronome-selects.hidden + select.metronome-select.metro-sound + option.label value="bleep" bleep + option.label value="click" click + option.label value="drum" drum + br + select.metronome-select.metro-tempo + - metronome_tempos.each do |t| + option.label value=t + =t \ No newline at end of file From 9ae7b425ad0224635b07fb6a666343956f9d888f Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Tue, 27 Jan 2015 14:58:59 -0600 Subject: [PATCH 14/46] Restore missing tags. --- web/app/views/clients/_session.html.slim | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index d5cd7fac3..19e33b0b9 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -129,4 +129,12 @@ script#template-session-track[type="text/template"] select.metronome-select.metro-tempo - metronome_tempos.each do |t| option.label value=t - =t \ No newline at end of file + =t + +script#template-option type="text/template" + option value="{value}" title="{label}" selected="{selected}" + ="{label}" + +script#template-genre-option type="text/template" + option value="{value}" + ="{label}" \ No newline at end of file From aa20f77eaf50818acaa483e62e0a317c8d5a4114 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 28 Jan 2015 16:08:47 +0000 Subject: [PATCH 15/46] *Bump --- web/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/README.md b/web/README.md index ae45c560b..efb2ab112 100644 --- a/web/README.md +++ b/web/README.md @@ -11,5 +11,3 @@ $ bundle $ rake jasmine Open browser to localhost:8888 - - From d7fc313d5f5d0609e500f3a6ebca3ac4fe335819 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 28 Jan 2015 19:17:20 -0600 Subject: [PATCH 16/46] VRFS-2665, VRFS-2666 : Create models and relationships for two new tables: backing_tracks and recorded_backing_tracks --- db/manifest | 3 +- db/up/recorded_backing_tracks.sql | 38 +++++++++++++++++++ ruby/lib/jam_ruby.rb | 4 +- ruby/lib/jam_ruby/models/backing_track.rb | 20 ++++++++++ ruby/lib/jam_ruby/models/connection.rb | 1 + .../jam_ruby/models/recorded_backing_track.rb | 18 +++++++++ ruby/spec/factories.rb | 5 +++ 7 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 db/up/recorded_backing_tracks.sql create mode 100644 ruby/lib/jam_ruby/models/backing_track.rb create mode 100644 ruby/lib/jam_ruby/models/recorded_backing_track.rb diff --git a/db/manifest b/db/manifest index 161ba7501..e0eeb557b 100755 --- a/db/manifest +++ b/db/manifest @@ -244,4 +244,5 @@ jamtracks_job.sql text_messages.sql text_message_migration.sql backing_tracks.sql -metronome.sql \ No newline at end of file +metronome.sql +recorded_backing_tracks.sql diff --git a/db/up/recorded_backing_tracks.sql b/db/up/recorded_backing_tracks.sql new file mode 100644 index 000000000..2b59895a0 --- /dev/null +++ b/db/up/recorded_backing_tracks.sql @@ -0,0 +1,38 @@ +CREATE TABLE backing_tracks ( + id VARCHAR(64) NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(), + filename VARCHAR(1024) NOT NULL, + + connection_id VARCHAR(64) NOT NULL, + client_track_id VARCHAR(64) NOT NULL, + client_resource_id VARCHAR(100), + + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE recorded_backing_tracks ( + id BIGINT PRIMARY KEY, + user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, + backing_track_id VARCHAR(64) REFERENCES backing_tracks(id) ON DELETE CASCADE, + recording_id VARCHAR(64) NOT NULL, + + client_track_id VARCHAR(64) NOT NULL, + is_part_uploading BOOLEAN NOT NULL DEFAULT FALSE, + next_part_to_upload INTEGER NOT NULL DEFAULT 0, + upload_id CHARACTER VARYING(1024), + part_failures INTEGER NOT NULL DEFAULT 0, + discard BOOLEAN, + download_count INTEGER NOT NULL DEFAULT 0, + md5 CHARACTER VARYING(100), + length BIGINT, + client_id VARCHAR(64) NOT NULL, + file_offset BIGINT, + + url VARCHAR(1024) NOT NULL, + fully_uploaded BOOLEAN NOT NULL DEFAULT FALSE, + upload_failures INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +ALTER TABLE recorded_backing_tracks ALTER COLUMN id SET DEFAULT nextval('tracks_next_tracker_seq'); diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 084ae7d9e..f707fcf27 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -86,6 +86,7 @@ require "jam_ruby/lib/stats.rb" require "jam_ruby/amqp/amqp_connection_manager" require "jam_ruby/database" require "jam_ruby/message_factory" +require "jam_ruby/models/backing_track" require "jam_ruby/models/feedback" require "jam_ruby/models/feedback_observer" #require "jam_ruby/models/max_mind_geo" @@ -131,8 +132,10 @@ require "jam_ruby/models/search" require "jam_ruby/models/recording" require "jam_ruby/models/recording_comment" require "jam_ruby/models/recording_liker" +require "jam_ruby/models/recorded_backing_track" require "jam_ruby/models/recorded_track" require "jam_ruby/models/recorded_track_observer" +require "jam_ruby/models/recorded_video" require "jam_ruby/models/quick_mix" require "jam_ruby/models/quick_mix_observer" require "jam_ruby/models/share_token" @@ -196,7 +199,6 @@ require "jam_ruby/models/score_history" require "jam_ruby/models/jam_company" require "jam_ruby/models/user_sync" require "jam_ruby/models/video_source" -require "jam_ruby/models/recorded_video" require "jam_ruby/models/text_message" require "jam_ruby/jam_tracks_manager" diff --git a/ruby/lib/jam_ruby/models/backing_track.rb b/ruby/lib/jam_ruby/models/backing_track.rb new file mode 100644 index 000000000..66304e333 --- /dev/null +++ b/ruby/lib/jam_ruby/models/backing_track.rb @@ -0,0 +1,20 @@ +module JamRuby + class BackingTrack < ActiveRecord::Base + + self.table_name = "backing_tracks" + self.primary_key = 'id' + + default_scope order('created_at ASC') + + belongs_to :connection, :class_name => "JamRuby::Connection", :inverse_of => :tracks, :foreign_key => 'connection_id' + validates :connection, presence: true + validates :client_track_id, presence: true + + def user + self.connection.user + end + + + + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/connection.rb b/ruby/lib/jam_ruby/models/connection.rb index 8a813f9ce..56591a318 100644 --- a/ruby/lib/jam_ruby/models/connection.rb +++ b/ruby/lib/jam_ruby/models/connection.rb @@ -18,6 +18,7 @@ module JamRuby belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", foreign_key: :music_session_id has_one :latency_tester, class_name: 'JamRuby::LatencyTester', foreign_key: :client_id, primary_key: :client_id has_many :tracks, :class_name => "JamRuby::Track", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all + has_many :backing_tracks, :class_name => "JamRuby::BackingTrack", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all has_many :video_sources, :class_name => "JamRuby::VideoSource", :inverse_of => :connection, :foreign_key => 'connection_id', :dependent => :delete_all validates :as_musician, :inclusion => {:in => [true, false, nil]} diff --git a/ruby/lib/jam_ruby/models/recorded_backing_track.rb b/ruby/lib/jam_ruby/models/recorded_backing_track.rb new file mode 100644 index 000000000..d3b3493ae --- /dev/null +++ b/ruby/lib/jam_ruby/models/recorded_backing_track.rb @@ -0,0 +1,18 @@ +module JamRuby + # BackingTrack analog to JamRuby::RecordedTrack + class RecordedBackingTrack < ActiveRecord::Base + belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_backing_tracks + belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_backing_tracks + validates :filename, :presence => true + + def self.create_from_backing_track(backing_track, recording) + recorded_backing_track = self.new + recorded_backing_track.recording = recording + recorded_backing_track.client_backing_track_id = backing_track.client_backing_track_id + recorded_backing_track.user = backing_track.connection.user + recorded_backing_track.save + recorded_backing_track + end + + end +end diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index bd52cc17f..717ff789e 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -237,6 +237,11 @@ FactoryGirl.define do sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"} end + factory :backing_track, :class => JamRuby::BackingTrack do + sequence(:client_track_id) { |n| "client_track_id#{n}"} + filename 'foo.mp3' + end + factory :recorded_track, :class => JamRuby::RecordedTrack do instrument JamRuby::Instrument.first sound 'stereo' From 63574f5a253dc8c19c9d963b5cf252acbf9f36d4 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Wed, 28 Jan 2015 19:22:28 -0600 Subject: [PATCH 17/46] VRFS-2667, VRFS-2668 : list_uploads and list_downloads implementation and tests. --- ruby/lib/jam_ruby/models/recording.rb | 79 +++++++++++++++++---- ruby/spec/jam_ruby/models/recording_spec.rb | 2 + 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 49f4219f1..2863dc34d 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -11,6 +11,7 @@ module JamRuby has_many :quick_mixes, :class_name => "JamRuby::QuickMix", :foreign_key => :recording_id, :dependent => :destroy has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy has_many :recorded_videos, :class_name => "JamRuby::RecordedVideo", :foreign_key => :recording_id, :dependent => :destroy + has_many :recorded_backing_tracks, :class_name => "JamRuby::RecordedBackingTrack", :foreign_key => :recording_id, :dependent => :destroy has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy @@ -209,6 +210,10 @@ module JamRuby connection.video_sources.each do |video| recording.recorded_videos << RecordedVideo.create_from_video_source(video, recording) end + + connection.backing_tracks.each do |backing_track| + recording.recorded_videos << RecordedBackingTrack.create_from_backing_track(backing_track, recording) + end end end end @@ -321,8 +326,7 @@ module JamRuby } ) end - - latest_recorded_track = downloads[-1][:next] if downloads.length > 0 + latest_recorded_track = (downloads.length > 0) ? downloads[-1][:next] : nil Mix.joins(:recording).joins(:recording => :claimed_recordings) .order('mixes.id') @@ -345,16 +349,30 @@ module JamRuby } ) end + latest_mix = (downloads.length > 0) ? downloads[-1][:next] : nil - latest_mix = downloads[-1][:next] if downloads.length > 0 - - if !latest_mix.nil? && !latest_recorded_track.nil? - next_date = [latest_mix, latest_recorded_track].max - elsif latest_mix.nil? - next_date = latest_recorded_track - else - next_date = latest_mix + RecordedBackingTrack.joins(:recording).joins(:recording => :claimed_recordings) + .order('recorded_backing_tracks.id') + .where('recorded_backing_tracks.fully_uploaded = TRUE') + .where('recorded_backing_tracks.id > ?', since) + .where('all_discarded = false') + .where('deleted = false') + .where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_backing_track| + downloads.push( + { + :type => "recorded_backing_track", + :id => recorded_backing_track.client_track_id, + :recording_id => recorded_backing_track.recording_id, + :length => recorded_backing_track.length, + :md5 => recorded_backing_track.md5, + :url => recorded_backing_track[:url], + :next => recorded_backing_track.id + } + ) end + latest_recorded_backing_track = (downloads.length > 0) ? downloads[-1][:next] : nil + + next_date = [latest_mix, latest_recorded_track, latest_recorded_backing_track].max if next_date.nil? next_date = since # echo back to the client the same value they passed in, if there are no results @@ -417,6 +435,20 @@ module JamRuby Arel::Nodes::As.new('stream_mix', Arel.sql('item_type')) ]).reorder("") + # Select fields for quick mix. Note that it must include + # the same number of fields as the track or video in order for + # the union to work: + backing_track_arel = RecordedBackingTrack.select([ + :id, + :recording_id, + :user_id, + :url, + :fully_uploaded, + :upload_failures, + Arel::Nodes::As.new('', Arel.sql('backing_track_track_id')), + Arel::Nodes::As.new('backing_track', Arel.sql('item_type')) + ]).reorder("") + # Glue them together: union = track_arel.union(vid_arel) @@ -439,7 +471,25 @@ module JamRuby ]) # And repeat: - union_all = arel.union(quick_mix_arel) + union_quick = arel.union(quick_mix_arel) + utable_quick = Arel::Nodes::TableAlias.new(union_quick, :recorded_items_quick) + arel = arel.from(utable_quick) + + arel = arel.except(:select) + arel = arel.select([ + "recorded_items_quick.id", + :recording_id, + :user_id, + :url, + :fully_uploaded, + :upload_failures, + :client_track_id, + :item_type + ]) + + + # And repeat for backing track: + union_all = arel.union(backing_track_arel) utable_all = Arel::Nodes::TableAlias.new(union_all, :recorded_items_all) arel = arel.from(utable_all) @@ -455,7 +505,6 @@ module JamRuby :item_type ]) - # Further joining and criteria for the unioned object: arel = arel.joins("INNER JOIN recordings ON recordings.id=recorded_items_all.recording_id") \ .where('recorded_items_all.user_id' => user.id) \ @@ -492,6 +541,12 @@ module JamRuby :recording_id => recorded_item.recording_id, :next => recorded_item.id }) + elsif recorded_item.item_type == 'backing_track' + uploads << ({ + :type => "backing_track", + :recording_id => recorded_item.recording_id, + :next => recorded_item.id + }) else end diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb index 303080174..a151bb43f 100644 --- a/ruby/spec/jam_ruby/models/recording_spec.rb +++ b/ruby/spec/jam_ruby/models/recording_spec.rb @@ -547,6 +547,8 @@ describe Recording do @genre = FactoryGirl.create(:genre) @recording.claim(@user, "Recording", "Recording Description", @genre, true) + @backing_track = FactoryGirl.create(:backing_track, :connection => @connection) + # We should have 2 items; a track and a video: uploads = Recording.list_uploads(@user) uploads["uploads"].should have(3).items From 5ed67cc8f661f85c1a58aad2ee2b6c8795421f2b Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 29 Jan 2015 09:50:13 -0600 Subject: [PATCH 18/46] VRFS-2667, VRFS-2668 : Fix broken spec. --- ruby/lib/jam_ruby/models/recording.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 2863dc34d..ca1c241ef 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -326,7 +326,7 @@ module JamRuby } ) end - latest_recorded_track = (downloads.length > 0) ? downloads[-1][:next] : nil + latest_recorded_track = (downloads.length > 0) ? downloads[-1][:next] : 0 Mix.joins(:recording).joins(:recording => :claimed_recordings) .order('mixes.id') @@ -349,7 +349,7 @@ module JamRuby } ) end - latest_mix = (downloads.length > 0) ? downloads[-1][:next] : nil + latest_mix = (downloads.length > 0) ? downloads[-1][:next] : 0 RecordedBackingTrack.joins(:recording).joins(:recording => :claimed_recordings) .order('recorded_backing_tracks.id') @@ -370,7 +370,7 @@ module JamRuby } ) end - latest_recorded_backing_track = (downloads.length > 0) ? downloads[-1][:next] : nil + latest_recorded_backing_track = (downloads.length > 0) ? downloads[-1][:next] : 0 next_date = [latest_mix, latest_recorded_track, latest_recorded_backing_track].max From d9d3e67fc5ab666f9389100de3ff535e3c32d43c Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 29 Jan 2015 16:11:57 -0600 Subject: [PATCH 19/46] VRFS-2582 : Hook up to client rather than stubbed API to list backing tracks. Implement in fakeJamClient for easy testing. --- .../dialog/openBackingTrackDialog.js | 52 ++++++++----------- web/app/assets/javascripts/fakeJamClient.js | 9 ++++ .../dialogs/_openBackingTrackDialog.html.slim | 2 +- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index 7a950a829..b1297c2e1 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -28,11 +28,11 @@ resetPagination(); showing = true; getBackingTracks() - .done(function(data, textStatus, jqXHR) { - // initialize pagination - var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected) - $paginatorHolder.append($paginator); - }); + // .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() { @@ -45,33 +45,25 @@ } function getBackingTracks(page) { - return rest.getBackingTracks({page:page + 1, per_page:10}) - .done(function(result) { - console.log("result: ", result) - var backingTracks = result.backing_tracks - console.log("Backing Tracks: ", backingTracks) - emptyList(); - $.each(backingTracks, function(index, backingTrack) { - console.log("Backing TRACK: ", backingTrack) - var options = { - backingTrackState: null, - path: backingTrack.path, - name: backingTrack.name, - length: backingTrack.length ? backingTrack.length : 0 - } - console.log("options: ", options) - var $tr = $(context._.template($templateOpenBackingTrackRow.html(), options, { variable: 'data' })); - console.log("foo") - $tr.data('server-model', backingTrack); - console.log("bar") - console.log("Appending TR:") - $tbody.append($tr); - }); - }) - .fail(function(jqXHR, textStatus, errorMessage) { - app.ajaxError(jqXHR, textStatus, errorMessage); + var backingTracks = context.jamClient.getBackingTrackList(); + console.log("Backing Tracks: ", backingTracks) + + if (typeof(backingTracks)=="undefined") { + $tbody.append("No Tracks found"); + } else { + $.each(backingTracks, function(index, backingTrack) { + var options = { + backingTrackState: null, + path: backingTrack.path, + name: backingTrack.name + //length: backingTrack.length ? backingTrack.length : 0 + } + var $tr = $(context._.template($templateOpenBackingTrackRow.html(), options, { variable: 'data' })); + $tr.data('server-model', backingTrack); + $tbody.append($tr); }); + }//end } diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 1a42a27b5..9d64bd256 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -784,6 +784,14 @@ fire(); } + + function getBackingTrackList() { + return [ + {name:'foo',path:"foobar.mp3", length:4283}, + {name:'bar',path:"foo.mp3",length:3257} + ]; + } + function ClientUpdateStartUpdate(path, successCallback, failureCallback) {} // ------------------------------- @@ -1044,6 +1052,7 @@ this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval; + this.getBackingTrackList = getBackingTrackList; this.SessionCloseBackingTrackFile = SessionCloseBackingTrackFile; this.SessionOpenBackingTrackFile = SessionOpenBackingTrackFile; diff --git a/web/app/views/dialogs/_openBackingTrackDialog.html.slim b/web/app/views/dialogs/_openBackingTrackDialog.html.slim index dec11d179..e3aee9d97 100644 --- a/web/app/views/dialogs/_openBackingTrackDialog.html.slim +++ b/web/app/views/dialogs/_openBackingTrackDialog.html.slim @@ -20,7 +20,7 @@ tbody br - .left.paginator-holder + / .left.paginator-holder .help-links a.what-are-backingtracks href='#' From a4d8ea5f60087cde1ece22db5d6840a6170a525b Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 29 Jan 2015 16:12:26 -0600 Subject: [PATCH 20/46] VRFS-2498 : Fix tempo and sound strings for initial metronome opening. --- web/app/assets/javascripts/session.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 697cfd4d6..8d41a0a0c 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -2174,8 +2174,8 @@ } else { rest.openMetronome({id: sessionModel.id()}) .done(function() { - context.JK.CurrentSessionModel.refreshCurrentSession(true) setMetronomeFromForm() + context.JK.CurrentSessionModel.refreshCurrentSession(true) }) .fail(function(jqXHR) { console.log(jqXHR, jqXHR) @@ -2334,9 +2334,23 @@ } function setMetronomeFromForm() { - var sound = $("select.metro-sound:visible option:selected").val() var tempo = $("select.metro-tempo:visible option:selected").val() - context.jamClient.SessionSetMetronome(tempo, sound, 1, 0) + var sound = $("select.metro-sound:visible option:selected").val() + + var t = parseInt(tempo); + var s; + if (tempo==NaN || tempo==0 || tempo==null) { + t = 120 + } + + if (sound==null || typeof(sound)=='undefined' || sound=="") { + s = "click" + } else { + s = sound + } + + console.log("Setting tempo and sound:", t, s) + context.jamClient.SessionSetMetronome(t, s, 1, 0) } function onMetronomeChanged(e, data) { From ac1271634d67f30f9d6ebcd6c844b33dcdcb6d1d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 31 Jan 2015 15:20:48 -0600 Subject: [PATCH 21/46] * VRFS-2716 - changes to deal with new getBackingList format, VRFS-2661 - accepting backing_track info in Track.sync (wip) --- db/manifest | 1 + .../recorded_backing_tracks_add_filename.sql | 1 + ruby/lib/jam_ruby/models/backing_track.rb | 1 + .../jam_ruby/models/recorded_backing_track.rb | 16 ++- ruby/lib/jam_ruby/models/recording.rb | 10 +- ruby/lib/jam_ruby/models/track.rb | 134 ++++++++++-------- ruby/lib/jam_ruby/models/user.rb | 1 + ruby/spec/jam_ruby/models/recording_spec.rb | 14 ++ ruby/spec/jam_ruby/models/track_spec.rb | 83 +++++++++-- .../dialog/openBackingTrackDialog.js | 13 +- web/app/assets/javascripts/fakeJamClient.js | 8 +- .../api_music_sessions_controller.rb | 2 +- web/lib/music_session_manager.rb | 4 +- 13 files changed, 204 insertions(+), 84 deletions(-) create mode 100644 db/up/recorded_backing_tracks_add_filename.sql diff --git a/db/manifest b/db/manifest index 14623b9b8..a95040283 100755 --- a/db/manifest +++ b/db/manifest @@ -249,3 +249,4 @@ recorded_backing_tracks.sql user_model_about_changes.sql performance_samples.sql user_presences.sql +recorded_backing_tracks_add_filename.sql \ No newline at end of file diff --git a/db/up/recorded_backing_tracks_add_filename.sql b/db/up/recorded_backing_tracks_add_filename.sql new file mode 100644 index 000000000..af959b9d5 --- /dev/null +++ b/db/up/recorded_backing_tracks_add_filename.sql @@ -0,0 +1 @@ +ALTER TABLE recorded_backing_tracks ADD COLUMN filename VARCHAR NOT NULL; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/backing_track.rb b/ruby/lib/jam_ruby/models/backing_track.rb index 66304e333..7437b2de7 100644 --- a/ruby/lib/jam_ruby/models/backing_track.rb +++ b/ruby/lib/jam_ruby/models/backing_track.rb @@ -9,6 +9,7 @@ module JamRuby belongs_to :connection, :class_name => "JamRuby::Connection", :inverse_of => :tracks, :foreign_key => 'connection_id' validates :connection, presence: true validates :client_track_id, presence: true + validates :filename, presence: true def user self.connection.user diff --git a/ruby/lib/jam_ruby/models/recorded_backing_track.rb b/ruby/lib/jam_ruby/models/recorded_backing_track.rb index d3b3493ae..4d8d5ff35 100644 --- a/ruby/lib/jam_ruby/models/recorded_backing_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_backing_track.rb @@ -8,11 +8,23 @@ module JamRuby def self.create_from_backing_track(backing_track, recording) recorded_backing_track = self.new recorded_backing_track.recording = recording - recorded_backing_track.client_backing_track_id = backing_track.client_backing_track_id + recorded_backing_track.client_id = backing_track.connection.client_id + recorded_backing_track.backing_track_id = backing_track.id + recorded_backing_track.client_track_id = backing_track.client_track_id recorded_backing_track.user = backing_track.connection.user - recorded_backing_track.save + recorded_backing_track.filename = backing_track.filename + recorded_backing_track.next_part_to_upload = 0 + recorded_backing_track.file_offset = 0 + recorded_backing_track[:url] = construct_filename(recording.created_at, recording.id, backing_track.client_track_id) + recorded_backing_track.save recorded_backing_track end + private + + def self.construct_filename(created_at, recording_id, client_track_id) + raise "unknown ID" unless client_track_id + "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/backing-track-#{client_track_id}.ogg" + end end end diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index ca1c241ef..cf1597ef3 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -180,6 +180,14 @@ module JamRuby recorded_tracks.where(:user_id => user.id) end + def recorded_backing_tracks_for_user(user) + unless self.users.exists?(user) + raise PermissionError, "user was not in this session" + end + recorded_backing_tracks.where(:user_id => user.id) + end + + def has_access?(user) users.exists?(user) end @@ -212,7 +220,7 @@ module JamRuby end connection.backing_tracks.each do |backing_track| - recording.recorded_videos << RecordedBackingTrack.create_from_backing_track(backing_track, recording) + recording.recorded_backing_tracks << RecordedBackingTrack.create_from_backing_track(backing_track, recording) end end end diff --git a/ruby/lib/jam_ruby/models/track.rb b/ruby/lib/jam_ruby/models/track.rb index 10b7ecb40..c06ad2d7a 100644 --- a/ruby/lib/jam_ruby/models/track.rb +++ b/ruby/lib/jam_ruby/models/track.rb @@ -55,11 +55,64 @@ module JamRuby return query end + def self.diff_track(track_class, existing_tracks, new_tracks, &blk) + result = [] + if new_tracks.length == 0 + existing_tracks.delete_all + else + + # we will prune from this as we find matching tracks + to_delete = Set.new(existing_tracks) + to_add = Array.new(new_tracks) + + existing_tracks.each do |existing_track| + new_tracks.each do |new_track| + + if new_track[:id] == existing_track.id || new_track[:client_track_id] == existing_track.client_track_id + to_delete.delete(existing_track) + to_add.delete(new_track) + + blk.call(existing_track, new_track) + + result.push(existing_track) + + if existing_track.save + next + else + result = existing_track + raise ActiveRecord::Rollback + end + end + end + end + + + to_add.each do |new_track| + existing_track = track_class.new + + blk.call(existing_track, new_track) + + if existing_track.save + result.push(existing_track) + else + result = existing_track + raise ActiveRecord::Rollback + end + end + + to_delete.each do |delete_me| + delete_me.delete + end + end + result + end # this is a bit different from a normal track synchronization in that the client just sends up all tracks, # ... some may already exist - def self.sync(clientId, tracks) - result = [] + def self.sync(clientId, tracks, backing_tracks = []) + result = {} + + backing_tracks = [] unless backing_tracks Track.transaction do connection = Connection.find_by_client_id!(clientId) @@ -68,67 +121,28 @@ module JamRuby msh = MusicSessionUserHistory.find_by_client_id!(clientId) instruments = [] - if tracks.length == 0 - connection.tracks.delete_all - else - connection_tracks = connection.tracks + tracks.each do |track| + instruments << track[:instrument_id] + end - # we will prune from this as we find matching tracks - to_delete = Set.new(connection_tracks) - to_add = Array.new(tracks) + result[:tracks] = diff_track(Track, connection.tracks, tracks) do |track_record, track_info| + track_record.connection = connection + track_record.client_track_id = track_info[:client_track_id] + track_record.client_resource_id = track_info[:client_resource_id] + track_record.instrument_id = track_info[:instrument_id] + track_record.sound = track_info[:sound] + end - tracks.each do |track| - instruments << track[:instrument_id] - end + result[:backing_tracks] = diff_track(BackingTrack, connection.backing_tracks, backing_tracks) do |track_record, track_info| + track_record.connection = connection + track_record.client_track_id = track_info[:client_track_id] + track_record.client_resource_id = track_info[:client_resource_id] + track_record.filename = track_info[:filename] + end - connection_tracks.each do |connection_track| - tracks.each do |track| - - if track[:id] == connection_track.id || track[:client_track_id] == connection_track.client_track_id - to_delete.delete(connection_track) - to_add.delete(track) - # don't update connection_id or client_id; it's unknown what would happen if these changed mid-session - connection_track.instrument_id = track[:instrument_id] - connection_track.sound = track[:sound] - connection_track.client_track_id = track[:client_track_id] - connection_track.client_resource_id = track[:client_resource_id] - - result.push(connection_track) - - if connection_track.save - next - else - result = connection_track - raise ActiveRecord::Rollback - end - - end - end - end - - msh.instruments = instruments.join("|") - if !msh.save - raise ActiveRecord::Rollback - end - - to_add.each do |track| - connection_track = Track.new - connection_track.connection = connection - connection_track.instrument_id = track[:instrument_id] - connection_track.sound = track[:sound] - connection_track.client_track_id = track[:client_track_id] - connection_track.client_resource_id = track[:client_resource_id] - if connection_track.save - result.push(connection_track) - else - result = connection_track - raise ActiveRecord::Rollback - end - end - - to_delete.each do |delete_me| - delete_me.delete - end + msh.instruments = instruments.join("|") + if !msh.save + raise ActiveRecord::Rollback end end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index 30bde5246..3bb4c270a 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -122,6 +122,7 @@ module JamRuby # saved tracks has_many :recorded_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedTrack", :inverse_of => :user has_many :recorded_videos, :foreign_key => "user_id", :class_name => "JamRuby::RecordedVideo", :inverse_of => :user + has_many :recorded_backing_tracks, :foreign_key => "user_id", :class_name => "JamRuby::RecordedBackingTrack", :inverse_of => :user has_many :quick_mixes, :foreign_key => "user_id", :class_name => "JamRuby::QuickMix", :inverse_of => :user # invited users diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb index a151bb43f..d2fd7c3b0 100644 --- a/ruby/spec/jam_ruby/models/recording_spec.rb +++ b/ruby/spec/jam_ruby/models/recording_spec.rb @@ -211,6 +211,20 @@ describe Recording do user1_recorded_tracks[0].discard = true user1_recorded_tracks[0].save! end + + it "should allow finding of backing tracks" do + user2 = FactoryGirl.create(:user) + connection2 = FactoryGirl.create(:connection, :user => user2, :music_session => @music_session) + track2 = FactoryGirl.create(:track, :connection => connection2, :instrument => @instrument) + backing_track = FactoryGirl.create(:backing_track, :connection => connection2) + + + @recording = Recording.start(@music_session, @user) + @recording.recorded_backing_tracks_for_user(@user).length.should eq(0) + user2_recorded_tracks = @recording.recorded_backing_tracks_for_user(user2) + user2_recorded_tracks.length.should == 1 + user2_recorded_tracks[0].should == user2.recorded_backing_tracks[0] + end it "should set up the recording properly when recording is started with 1 user in the session" do @music_session.is_recording?.should be_false diff --git a/ruby/spec/jam_ruby/models/track_spec.rb b/ruby/spec/jam_ruby/models/track_spec.rb index 1e03d123b..4b27ad062 100644 --- a/ruby/spec/jam_ruby/models/track_spec.rb +++ b/ruby/spec/jam_ruby/models/track_spec.rb @@ -7,8 +7,10 @@ describe Track do let (:connection) { FactoryGirl.create(:connection, :user => user, :music_session => music_session) } let (:track) { FactoryGirl.create(:track, :connection => connection)} let (:track2) { FactoryGirl.create(:track, :connection => connection)} + let (:backing_track) { FactoryGirl.create(:backing_track, :connection => connection)} let (:msuh) {FactoryGirl.create(:music_session_user_history, :history => music_session.music_session, :user => user, :client_id => connection.client_id) } let (:track_hash) { {:client_track_id => 'client_guid', :sound => 'stereo', :instrument_id => 'drums'} } + let (:backing_track_hash) { {:client_track_id => 'client_guid', :filename => "blah.wav"} } before(:each) do msuh.touch @@ -16,7 +18,8 @@ describe Track do describe "sync" do it "create one track" do - tracks = Track.sync(connection.client_id, [track_hash]) + result = Track.sync(connection.client_id, [track_hash]) + tracks = result[:tracks] tracks.length.should == 1 track = tracks[0] track.client_track_id.should == track_hash[:client_track_id] @@ -25,7 +28,8 @@ describe Track do end it "create two tracks" do - tracks = Track.sync(connection.client_id, [track_hash, track_hash]) + result = Track.sync(connection.client_id, [track_hash, track_hash]) + tracks = result[:tracks] tracks.length.should == 2 track = tracks[0] track.client_track_id.should == track_hash[:client_track_id] @@ -40,7 +44,8 @@ describe Track do it "delete only track" do track.id.should_not be_nil connection.tracks.length.should == 1 - tracks = Track.sync(connection.client_id, []) + result = Track.sync(connection.client_id, []) + tracks = result[:tracks] tracks.length.should == 0 end @@ -49,7 +54,8 @@ describe Track do track.id.should_not be_nil track2.id.should_not be_nil connection.tracks.length.should == 2 - tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}]) + result = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}]) + tracks = result[:tracks] tracks.length.should == 1 found = tracks[0] found.id.should == track.id @@ -62,7 +68,8 @@ describe Track do track.id.should_not be_nil track2.id.should_not be_nil connection.tracks.length.should == 2 - tracks = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}]) + result = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}]) + tracks = result[:tracks] tracks.length.should == 1 found = tracks[0] found.id.should == track.id @@ -75,7 +82,8 @@ describe Track do track.id.should_not be_nil connection.tracks.length.should == 1 set_updated_at(track, 1.days.ago) - tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}]) + result = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => 'client_guid_new', :sound => 'mono', :instrument_id => 'drums'}]) + tracks = result[:tracks] tracks.length.should == 1 found = tracks[0] found.id.should == track.id @@ -87,7 +95,8 @@ describe Track do it "updates a single track using .client_track_id to correlate" do track.id.should_not be_nil connection.tracks.length.should == 1 - tracks = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}]) + result = Track.sync(connection.client_id, [{:client_track_id => track.client_track_id, :sound => 'mono', :instrument_id => 'drums'}]) + tracks = result[:tracks] tracks.length.should == 1 found = tracks[0] found.id.should == track.id @@ -99,11 +108,69 @@ describe Track do track.id.should_not be_nil connection.tracks.length.should == 1 set_updated_at(track, 1.days.ago) - tracks = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}]) + result = Track.sync(connection.client_id, [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}]) + tracks = result[:tracks] tracks.length.should == 1 found = tracks[0] expect(found.id).to eq track.id expect(found.updated_at.to_i).to eq track.updated_at.to_i end + + describe "backing tracks" do + it "create one track and one backing track" do + result = Track.sync(connection.client_id, [track_hash], [backing_track_hash]) + tracks = result[:tracks] + tracks.length.should == 1 + track = tracks[0] + track.client_track_id.should == track_hash[:client_track_id] + track.sound = track_hash[:sound] + track.instrument.should == Instrument.find('drums') + + backing_tracks = result[:backing_tracks] + backing_tracks.length.should == 1 + track = backing_tracks[0] + track.client_track_id.should == backing_track_hash[:client_track_id] + end + + it "delete only backing_track" do + track.id.should_not be_nil + backing_track.id.should_not be_nil + connection.tracks.length.should == 1 + connection.backing_tracks.length.should == 1 + result = Track.sync(connection.client_id, + [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}], + []) + tracks = result[:tracks] + tracks.length.should == 1 + found = tracks[0] + expect(found.id).to eq track.id + expect(found.updated_at.to_i).to eq track.updated_at.to_i + + backing_tracks = result[:backing_tracks] + backing_tracks.length.should == 0 + end + + it "does not touch updated_at when nothing changes" do + track.id.should_not be_nil + backing_track.id.should_not be_nil + connection.tracks.length.should == 1 + set_updated_at(track, 1.days.ago) + set_updated_at(backing_track, 1.days.ago) + result = Track.sync(connection.client_id, + [{:id => track.id, :client_track_id => track.client_track_id, :sound => track.sound, :instrument_id => track.instrument_id, client_resource_id: track.client_resource_id}], + [{:id => backing_track.id, :client_track_id => backing_track.client_track_id, :filename => backing_track.filename, client_resource_id: backing_track.client_resource_id}]) + tracks = result[:tracks] + tracks.length.should == 1 + found = tracks[0] + expect(found.id).to eq track.id + expect(found.updated_at.to_i).to eq track.updated_at.to_i + + backing_tracks = result[:backing_tracks] + backing_tracks.length.should == 1 + found = backing_tracks[0] + expect(found.id).to eq backing_track.id + expect(found.updated_at.to_i).to eq backing_track.updated_at.to_i + end + end end end \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index b1297c2e1..309084c82 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -46,18 +46,19 @@ function getBackingTracks(page) { - var backingTracks = context.jamClient.getBackingTrackList(); - console.log("Backing Tracks: ", backingTracks) + var result = context.jamClient.getBackingTrackList(); + console.log("result", result) + var backingTracks = result.backing_tracks; - if (typeof(backingTracks)=="undefined") { + if (!backingTracks || backingTracks.length == 0) { $tbody.append("No Tracks found"); } else { $.each(backingTracks, function(index, backingTrack) { var options = { backingTrackState: null, - path: backingTrack.path, - name: backingTrack.name - //length: backingTrack.length ? backingTrack.length : 0 + path: backingTrack.name, + name: backingTrack.name, + length: backingTrack.size ? backingTrack.size : 0 } var $tr = $(context._.template($templateOpenBackingTrackRow.html(), options, { variable: 'data' })); $tr.data('server-model', backingTrack); diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 9d64bd256..406e4b7b0 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -786,10 +786,10 @@ } function getBackingTrackList() { - return [ - {name:'foo',path:"foobar.mp3", length:4283}, - {name:'bar',path:"foo.mp3",length:3257} - ]; + return {backing_tracks: [ + {name:"foobar.mp3", size:4283}, + {name:"foo.mp3",size:3257} + ]}; } function ClientUpdateStartUpdate(path, successCallback, failureCallback) {} diff --git a/web/app/controllers/api_music_sessions_controller.rb b/web/app/controllers/api_music_sessions_controller.rb index 20d34b823..e5f6a7bd4 100644 --- a/web/app/controllers/api_music_sessions_controller.rb +++ b/web/app/controllers/api_music_sessions_controller.rb @@ -357,7 +357,7 @@ class ApiMusicSessionsController < ApiController end def track_sync - @tracks = MusicSessionManager.new.sync_tracks(@music_session, params[:client_id], params[:tracks]) + @tracks = MusicSessionManager.new.sync_tracks(@music_session, params[:client_id], params[:tracks], params[:backing_tracks]) unless @tracks.kind_of? Array # we have to do this because api_session_detail_url will fail with a bad @tracks diff --git a/web/lib/music_session_manager.rb b/web/lib/music_session_manager.rb index a8259c804..706b41d83 100644 --- a/web/lib/music_session_manager.rb +++ b/web/lib/music_session_manager.rb @@ -140,10 +140,10 @@ class MusicSessionManager < BaseManager Notification.send_session_depart(active_music_session, connection.client_id, user, recordingId) end - def sync_tracks(active_music_session, client_id, new_tracks) + def sync_tracks(active_music_session, client_id, new_tracks, backing_tracks) tracks = nil active_music_session.with_lock do # VRFS-1297 - tracks = Track.sync(client_id, new_tracks) + tracks = Track.sync(client_id, new_tracks, backing_tracks) active_music_session.tick_track_changes end Notification.send_tracks_changed(active_music_session) From cb634392198e7bcef69ef81f092b0db70909937f Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 31 Jan 2015 15:28:53 -0600 Subject: [PATCH 22/46] * VRFS-2716 - reference to .path in SessionOpenBackingTrack changed to .name --- web/app/assets/javascripts/dialog/openBackingTrackDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index 309084c82..6cd292266 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -73,9 +73,9 @@ var backingTrack = $(this).data('server-model'); // tell the server we are about to open a backing track: - rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.path}) + rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.name}) .done(function(response) { - var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.path); + var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.name); console.log("BackingTrackPlay response: %o", result); // TODO: Possibly actually check the result. Investigate From d8c000c7f7f943fa94d3ab831e1e4573c958c6fa Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sat, 31 Jan 2015 17:57:54 -0600 Subject: [PATCH 23/46] VRFS-2716 : Compare proper string for backing track type. Change a few client calls to correct function names. --- web/app/assets/javascripts/session.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 8d41a0a0c..d0ec218d7 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -843,7 +843,7 @@ if(mediaType == 'RecordingTrack') { recordingTrackMixers.push(mixer) - } else if(mediaType == 'PeerMediaTrack') { + } else if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { // BackingTrack backingTrackMixers.push(mixer); } else if(mediaType == 'MetronomeTrack') { @@ -882,7 +882,6 @@ function renderBackingTracks(backingTrackMixers) { var backingTrack = sessionModel.backingTrack() var backingTrackPath = backingTrack ? backingTrack.path : null - console.log("rendering backing track",backingTrackPath) // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener @@ -2174,7 +2173,8 @@ } else { rest.openMetronome({id: sessionModel.id()}) .done(function() { - setMetronomeFromForm() + context.jamClient.SessionOpenMetronome(120, "click", 1, 0) + context.JK.CurrentSessionModel.refreshCurrentSession(true) context.JK.CurrentSessionModel.refreshCurrentSession(true) }) .fail(function(jqXHR) { @@ -2241,7 +2241,7 @@ }); }); - context.jamClient.closeBackingTrackFile(); + context.jamClient.SessionCloseBackingTrackFile(); return false; } From 8234c4c510be557421decf82044ac4eeafb3c75a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 1 Feb 2015 14:15:17 -0600 Subject: [PATCH 24/46] * VRFS-2726 - media_type is not set for develop builds of the client; so null means Recording in develop branch --- web/app/assets/javascripts/session.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index d0ec218d7..c6f8ae71c 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -841,7 +841,8 @@ context._.each(mixers, function(mixer) { var mediaType = mixer.media_type; - if(mediaType == 'RecordingTrack') { + // mediaType == null is for backwards compat with older clients. Can be removed soon + if(mediaType == null || mediaType == "" || mediaType == 'RecordingTrack') { recordingTrackMixers.push(mixer) } else if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { // BackingTrack From e12cbe783c0ef49baafc22febe96fe6187efc6b9 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 1 Feb 2015 17:09:53 -0600 Subject: [PATCH 25/46] VRFS-2717 : Show type (extension) and file length in MB. --- .../dialog/openBackingTrackDialog.js | 19 ++++++++++++++++--- .../dialogs/openBackingTrackDialog.css.scss | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index 6cd292266..a57cd48d2 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -13,6 +13,7 @@ var $templateOpenBackingTrackRow = null; var $downloadedTrackHelp = null; var $whatAreBackingTracks = null; + var $displayAudioFileFolder = null; function emptyList() { @@ -54,19 +55,30 @@ $tbody.append("No Tracks found"); } else { $.each(backingTracks, function(index, backingTrack) { + var extension = backingTrack.name var options = { backingTrackState: null, - path: backingTrack.name, name: backingTrack.name, - length: backingTrack.size ? backingTrack.size : 0 + type: getExtension(backingTrack.name), + length: displaySize(backingTrack.size) } var $tr = $(context._.template($templateOpenBackingTrackRow.html(), options, { variable: 'data' })); $tr.data('server-model', backingTrack); $tbody.append($tr); - }); + }); }//end } + // from http://stackoverflow.com/questions/190852/how-can-i-get-file-extensions-with-javascript + function getExtension(filename) { + return filename.substr((~-filename.lastIndexOf(".") >>> 0) + 2) + } + + // from seth: + function displaySize(length) { + var size = (length==null || typeof(length)=='undefined') ? 0 : Number(length) + return (Math.round(size * 10 / (1024 * 1024) ) / 10).toString() + "M" + } function registerStaticEvents() { $tbody.on('click', 'tr', function(e) { @@ -113,6 +125,7 @@ $paginatorHolder = $dialog.find('.paginator-holder'); $templateOpenBackingTrackRow = $('#template-backing-track-row') $whatAreBackingTracks = $dialog.find('.what-are-backingtracks') + $displayAudioFileFolder = $dialog.find('.display-backingtracks-folder') registerStaticEvents(); }; diff --git a/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss b/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss index 830d5c61c..06b4ffb2e 100644 --- a/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/openBackingTrackDialog.css.scss @@ -20,11 +20,11 @@ } .help-links { - text-align: center; + text-align: left; position: absolute; margin: 0 auto; width: 70%; - left: 15%; + //left: 15%; font-size: 12px; padding-top:5px; From a10078260a73ae0de2b7a1544fb3931387756681 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 1 Feb 2015 17:14:22 -0600 Subject: [PATCH 26/46] VRFS-2722 : Styling and logic necessary to display long labels on backing and other tracks. --- web/app/assets/javascripts/fakeJamClient.js | 4 +-- web/app/assets/javascripts/session.js | 27 ++++++++++++------ .../stylesheets/client/session.css.scss | 28 +++++++++++++------ web/app/views/clients/_session.html.slim | 3 +- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 406e4b7b0..45fb1299e 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -787,8 +787,8 @@ function getBackingTrackList() { return {backing_tracks: [ - {name:"foobar.mp3", size:4283}, - {name:"foo.mp3",size:3257} + {name:"This is a really long name for a song dude.mp3", size:4283}, + {name:"foo.mp3",size:325783838} ]}; } diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index c6f8ae71c..2477cdf7d 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -515,12 +515,21 @@ if ($('.session-livetracks .track').length === 0) { $('.session-livetracks .when-empty').show(); } + if ($('.session-recordings .track').length === 0) { $('.session-recordings .when-empty').show(); $('.session-recording-name-wrapper').hide(); $('.session-recordings .recording-controls').hide(); + } else { + $('.session-recordings .when-empty').hide(); + $('.session-recording-name-wrapper').show(); + $('.session-recordings .recording-controls').show(); } - } + + // Handle long labels: + $(".track-label").dotdotdot() + $(".session-recording-name").dotdotdot() + } // renderSession function _initDialogs() { configureTrackDialog.initialize(); @@ -877,13 +886,17 @@ if(adhocTrackMixers.length > 0) { logger.warn("some tracks are open that we don't know how to show") } + + } function renderBackingTracks(backingTrackMixers) { var backingTrack = sessionModel.backingTrack() var backingTrackPath = backingTrack ? backingTrack.path : null - + var name = backingTrackPath + console.log("Opening backing track ", backingTrackPath, backingTrack) + // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener var isOpener = backingTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup; @@ -892,10 +905,9 @@ // using the server's info in conjuction with the client's, draw the recording tracks if(backingTrackPath && backingTrackMixers.length > 0) { var backingTrack = {path: backingTrackPath} - var name = sessionModel.getCurrentSession().backing_track_path + //backingTrackPath sessionModel.getCurrentSession().backing_track_path $('.session-recording-name').text(name); - var noCorrespondingTracks = false; var mixer = backingTrackMixers[0] var preMasteredClass = ""; @@ -1119,7 +1131,7 @@ var trackData = { trackId: "MS" + oneOfTheTracks.id, clientId: oneOfTheTracks.client_id, - name: "Volume & Sound", + name: "Metronome", instrumentIcon: photoUrl, avatar: instrumentIcon, latency: "good", @@ -1662,10 +1674,7 @@ function _addRecordingTrack(trackData) { var parentSelector = '#session-recordedtracks-container'; var $destination = $(parentSelector); - $('.session-recordings .when-empty').hide(); - $('.session-recording-name-wrapper').show(); - $('.session-recordings .recording-controls').show(); - + var template = $('#template-session-track').html(); var newTrack = $(context.JK.fillTemplate(template, trackData)); $destination.append(newTrack); diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index a037f7e19..6c3823b6f 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -119,8 +119,21 @@ .session-recording-name-wrapper{ position:relative; - white-space:nowrap; - display:none; + white-space:normal; + display:none; + + .session-recording-name { + position:relative; + margin-top:9px; + margin-bottom:8px; + font-size:16px; + height: 22px; + min-height: 22px; + max-height: 22px; + display: inline-block; + width:60%; + text-overflow:ellipsis; + } .session-add { margin-top:9px; @@ -135,13 +148,6 @@ } } - .session-recording-name { - width:60%; - overflow:hidden; - margin-top:9px; - margin-bottom:8px; - font-size:16px; - } } @@ -210,6 +216,9 @@ table.vu td { position: absolute; text-align:center; width: 55px; + height: 15px; + min-height: 11px; + max-height: 33px; max-width: 55px; white-space:normal; top: 3px; @@ -217,6 +226,7 @@ table.vu td { font-family: Arial, Helvetica, sans-serif; font-size: 11px; font-weight: bold; + text-overflow:ellipsis; } .track-close { diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index 19e33b0b9..a096216a9 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -108,7 +108,8 @@ script#template-session-track[type="text/template"] .session-track.track client-id="{clientId}" track-id="{trackId}" .track-vu-left.mixer-id="{vuMixerId}_vul" .track-vu-right.mixer-id="{vuMixerId}_vur" - .track-label="{name}" + .track-label + span.name-text="{name}" #div-track-close.track-close.op30 track-id="{trackId}" =image_tag("content/icon_closetrack.png", {width: 12, height: 12}) div class="{avatarClass}" From 2abd5976106f8452be7682e6bed8fd15b090683e Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 1 Feb 2015 17:15:12 -0600 Subject: [PATCH 27/46] VRFS-2717 : Show length and type on backing track dialog - UI. --- .../views/dialogs/_openBackingTrackDialog.html.slim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/app/views/dialogs/_openBackingTrackDialog.html.slim b/web/app/views/dialogs/_openBackingTrackDialog.html.slim index e3aee9d97..875449387 100644 --- a/web/app/views/dialogs/_openBackingTrackDialog.html.slim +++ b/web/app/views/dialogs/_openBackingTrackDialog.html.slim @@ -14,15 +14,17 @@ th align="left" | NAME th align="left" - | PATH + | SIZE th align="left" - | LENGTH + | TYPE tbody br / .left.paginator-holder .help-links + a.display-backingtracks-folder href='#' + | Display audio file folder a.what-are-backingtracks href='#' | What are Backing Tracks? .right @@ -35,8 +37,8 @@ td | {{data.name}} td - | {{data.path}} + | {{data.length}} td - | {{data.length}} + | {{data.type}} From eff19235c3478274564e4ce0c479d62cfbb56e91 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 1 Feb 2015 17:59:34 -0600 Subject: [PATCH 28/46] VRFS-2730 : Hook up jamtrack looping. --- .../javascripts/dialog/openBackingTrackDialog.js | 7 ++++++- web/app/assets/javascripts/fakeJamClient.js | 8 +++++++- web/app/assets/javascripts/session.js | 12 ++++++++++++ .../assets/stylesheets/client/session.css.scss | 16 ++++++++++++++++ web/app/views/clients/_session.html.slim | 2 ++ 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index a57cd48d2..82038a760 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -87,7 +87,7 @@ // tell the server we are about to open a backing track: rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: backingTrack.name}) .done(function(response) { - var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.name); + var result = context.jamClient.SessionOpenBackingTrackFile(backingTrack.name, false); console.log("BackingTrackPlay response: %o", result); // TODO: Possibly actually check the result. Investigate @@ -110,6 +110,11 @@ context.JK.helpBubble($whatAreBackingTracks, 'no help yet for this topic', {}, {positions:['bottom'], offsetParent: $dialog}) $whatAreBackingTracks.on('click', false) // no help yet + + $displayAudioFileFolder.on('click', function(e) { + e.stopPropagation(); + context.jamClient.OpenBackingTracksDirectory(); + }) } function initialize(){ diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 45fb1299e..f4b1db377 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -727,10 +727,15 @@ function SetScoreWorkTimingInterval(knobs) {return true;} function SessionOpenBackingTrackFile(path, loop) { - backingTrackPath=path + backingTrackPath = path backingTrackLoop = loop } + function SessionSetBackingTrackFileLoop(path, loop) { + backingTrackPath = path + backingTrackLoop = loop + } + function SessionCloseBackingTrackFile(path) { backingTrackPath="" } @@ -1055,6 +1060,7 @@ this.getBackingTrackList = getBackingTrackList; this.SessionCloseBackingTrackFile = SessionCloseBackingTrackFile; this.SessionOpenBackingTrackFile = SessionOpenBackingTrackFile; + this.SessionSetBackingTrackFileLoop = SessionSetBackingTrackFileLoop; this.SessionCloseMetronome = SessionCloseMetronome; this.SessionOpenMetronome = SessionOpenMetronome; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 2477cdf7d..53b7fb4fb 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -945,6 +945,7 @@ latency: "good", gainPercent: 0, muteClass: 'muted', + showLoop: true, mixerId: "", avatarClass : 'avatar-recording', preMasteredClass: "" @@ -1691,6 +1692,17 @@ $trackIconMute.data('media-controls-disabled', true).data('media-track-opener', trackData.mediaTrackOpener) } + if(trackData.showLoop) { + var $trackIconLoop = $track.find('.track-icon-loop') + $trackIconLoop.show() + var loopBtn = $trackIconLoop.find('#loop-button') + loopBtn.on('click', function(event) { + var loop = loopBtn.is(':checked') + console.log("Looping: ", loop); + context.jamClient.SessionSetBackingTrackFileLoop(trackData.name, loop) + }) + } + // is this used? tracks[trackData.trackId] = new context.JK.SessionTrack(trackData.clientId); } diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 6c3823b6f..6a49b01df 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -534,6 +534,22 @@ table.vu td { text-align: center; } +.track-icon-loop { + cursor: pointer; + position:absolute; + top:250px; + left:9; + width: 20px; + height: 18px; + // background-image:url('/assets/content/icon_mute.png'); + // background-repeat:no-repeat; + text-align: center; + input { + font-size: 8pt; + font-weight: bold; + } +} + .metronome-selects { position: absolute; width: 52px; diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index a096216a9..3c184cd2d 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -118,6 +118,8 @@ script#template-session-track[type="text/template"] img height="45" src="{instrumentIcon}" width="45" .track-gain mixer-id="{mixerId}" .track-icon-mute class="{muteClass}" control="mute" mixer-id="{muteMixerId}" + .track-icon-loop.hidden control="loop" + input#loop-button type="checkbox" value="loop" Loop .track-connection.grey mixer-id="{mixerId}_connection" CONNECTION .disabled-track-overlay From 957ecf6735b8046f67fc2b21d8ef05a4e9767bdd Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 1 Feb 2015 18:22:07 -0600 Subject: [PATCH 29/46] VRFS-2730 : Better styling for backing tracks loop checkbox. --- web/app/assets/stylesheets/client/session.css.scss | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 6a49b01df..3f107b2bd 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -538,16 +538,13 @@ table.vu td { cursor: pointer; position:absolute; top:250px; - left:9; + left:9px; width: 20px; height: 18px; - // background-image:url('/assets/content/icon_mute.png'); - // background-repeat:no-repeat; text-align: center; - input { - font-size: 8pt; - font-weight: bold; - } + font-size: 8pt; + font-weight: bold; + } .metronome-selects { From f80e9358f0faa78eb4e54a8ca817062af69b23b7 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 1 Feb 2015 19:29:21 -0600 Subject: [PATCH 30/46] Fix unit tests. --- monitor/spec/support/utilities.rb | 2 +- web/spec/features/in_session_spec.rb | 2 +- web/spec/support/utilities.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/monitor/spec/support/utilities.rb b/monitor/spec/support/utilities.rb index be0e32722..694d9e604 100755 --- a/monitor/spec/support/utilities.rb +++ b/monitor/spec/support/utilities.rb @@ -430,7 +430,7 @@ def assert_all_tracks_seen(users=[]) users.each do |user| in_client(user) do users.reject {|u| u==user}.each do |other| - find('div.track-label', text: other.name) + find('div.track-label > span', text: other.name) #puts user.name + " is able to see " + other.name + "\'s track" end end diff --git a/web/spec/features/in_session_spec.rb b/web/spec/features/in_session_spec.rb index 0d4215320..f40487b74 100644 --- a/web/spec/features/in_session_spec.rb +++ b/web/spec/features/in_session_spec.rb @@ -59,7 +59,7 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr end leave_music_session_sleep_delay - in_client(finder) { expect(page).to_not have_selector('div.track-label', text: user.name) } + in_client(finder) { expect(page).to_not have_selector('div.track-label > span', text: user.name) } end many = 4 diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 872bd736a..2b09adf4a 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -617,7 +617,7 @@ def assert_all_tracks_seen(users=[]) users.each do |user| in_client(user) do users.reject {|u| u==user}.each do |other| - find('div.track-label', text: other.name) + find('div.track-label > span', text: other.name) #puts user.name + " is able to see " + other.name + "\'s track" end end From 81dcd9bddc95106f45656e6421856aec9f17fc52 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 12 Feb 2015 14:05:25 -0600 Subject: [PATCH 31/46] VRFS-2498 : Hook up new metronome sounds. Check group_id in lieu of media_type as it is not set for metronomes. --- web/app/assets/javascripts/session.js | 52 +++++++++++-------- .../stylesheets/client/session.css.scss | 7 +-- web/app/views/clients/_session.html.slim | 10 ++-- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 53b7fb4fb..fff71eff5 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -44,6 +44,8 @@ var rateSessionDialog = null; var friendInput = null; var sessionPageDone = null; + var metroTempo = 120; + var metroSound = "Beep"; var $recordingManagerViewer = null; var $screen = null; var $mixModeDropdown = null; @@ -849,18 +851,18 @@ function groupByType(mixers) { context._.each(mixers, function(mixer) { var mediaType = mixer.media_type; - - // mediaType == null is for backwards compat with older clients. Can be removed soon - if(mediaType == null || mediaType == "" || mediaType == 'RecordingTrack') { - recordingTrackMixers.push(mixer) - } else if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { + if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { // BackingTrack backingTrackMixers.push(mixer); - } else if(mediaType == 'MetronomeTrack') { - // Metronome t + } else if(mediaType == 'MetronomeTrack' || mixer.group_id==ChannelGroupIds.MetronomeGroup) { + // Metronomes come across with a blank media type, so check group_id: metronomeTrackMixers.push(mixer); } else if(mediaType == 'JamTrack') { jamTrackMixers.push(mixer); + group_id == ChannelGroupIds.MediaTrackGroup; + } else if(mediaType == null || mediaType == "" || mediaType == 'RecordingTrack') { + // mediaType == null is for backwards compat with older clients. Can be removed soon + recordingTrackMixers.push(mixer) } else { console.log("Unknown track type: " + mediaType) adhocTrackMixers.push(mixer); @@ -913,7 +915,6 @@ var preMasteredClass = ""; // find the track or tracks that correspond to the mixer var correspondingTracks = [] - console.log("mixer", mixer) correspondingTracks.push(backingTrack); if(correspondingTracks.length == 0) { @@ -988,7 +989,6 @@ var preMasteredClass = ""; // find the track or tracks that correspond to the mixer var correspondingTracks = [] - console.log("mixer", mixer) $.each(jamTracks, function(i, jamTrack) { if(mixer.id.indexOf("L") == 0) { if(mixer.id.substring(1) == jamTrack.id) { @@ -1078,7 +1078,6 @@ // using the server's info in conjuction with the client's, draw the recording tracks if(metronomeActive && metronomeTrackMixers.length > 0) { - console.log("Rendering active metronome."); var metronome = {active: metronomeActive} $('.session-recording-name').text(name);//sessionModel.getCurrentSession().backing_track_path); @@ -1087,7 +1086,6 @@ var preMasteredClass = ""; // find the track or tracks that correspond to the mixer var correspondingTracks = [] - console.log("mixer", mixer) correspondingTracks.push(metronome); if(correspondingTracks.length == 0) { @@ -1160,10 +1158,9 @@ //trackData.mediaControlsDisabled = true; trackData.mediaTrackOpener = isOpener; } - _addRecordingTrack(trackData); - - + _addRecordingTrack(trackData); }// if + setFormFromMetronome() } @@ -1503,8 +1500,7 @@ } if (track.showMetronomeControls) { - $track.find('.metronome-selects').removeClass("hidden") - setMetronomeDefaults() + $track.find('.metronome-selects').removeClass("hidden") } else { $track.find('.metronome-selects').addClass("hidden") } @@ -1741,6 +1737,15 @@ // }); // } + function handleMetronomeCallback(args) { + console.log("MetronomeCallback: ", args) + metroTempo = args.bpm + + // This isn't actually there, so we rely on the metroSound as set from select on form: + // metroSound = args.sound + context.JK.CurrentSessionModel.refreshCurrentSession(true); + } + function handleVolumeChangeCallback(mixerId, isLeft, value, isMuted) { // Visually update mixer // There is no need to actually set the back-end mixer value as the @@ -2195,7 +2200,7 @@ } else { rest.openMetronome({id: sessionModel.id()}) .done(function() { - context.jamClient.SessionOpenMetronome(120, "click", 1, 0) + context.jamClient.SessionOpenMetronome(120, "Click", 1, 0) context.JK.CurrentSessionModel.refreshCurrentSession(true) context.JK.CurrentSessionModel.refreshCurrentSession(true) }) @@ -2351,16 +2356,17 @@ $(friendInput).show(); } - function setMetronomeDefaults() { - $("select.metro-tempo option[value='120']").attr('selected', 'selected'); + function setFormFromMetronome() { + $("select.metro-tempo").val(metroTempo) + $("select.metro-sound").val(metroSound) } function setMetronomeFromForm() { var tempo = $("select.metro-tempo:visible option:selected").val() var sound = $("select.metro-sound:visible option:selected").val() - var t = parseInt(tempo); - var s; + var t = parseInt(tempo) + var s if (tempo==NaN || tempo==0 || tempo==null) { t = 120 } @@ -2372,6 +2378,8 @@ } console.log("Setting tempo and sound:", t, s) + metroTempo = t + metroSound = s context.jamClient.SessionSetMetronome(t, s, 1, 0) } @@ -2436,6 +2444,7 @@ context.jamClient.SetVURefreshRate(150); context.jamClient.RegisterVolChangeCallBack("JK.HandleVolumeChangeCallback"); playbackControls = new context.JK.PlaybackControls($('.session-recordings .recording-controls')); + context.jamClient.setMetronomeOpenCallback("JK.HandleMetronomeCallback") var screenBindings = { 'beforeShow': beforeShow, @@ -2477,6 +2486,7 @@ } context.JK.HandleVolumeChangeCallback = handleVolumeChangeCallback; + context.JK.HandleMetronomeCallback = handleMetronomeCallback; context.JK.HandleBridgeCallback = handleBridgeCallback; }; diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 3f107b2bd..14f232941 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -564,11 +564,8 @@ table.vu td { margin: 0; width: 100% !important; font-size: 10px; - font-weight: normal; - //margin: 0 4px 0 4px; - background: transparent !important; - color: white; - } + font-weight: normal; + } } .track-icon-mute.muted { diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index 3c184cd2d..c765d4d61 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -124,12 +124,12 @@ script#template-session-track[type="text/template"] CONNECTION .disabled-track-overlay .metronome-selects.hidden - select.metronome-select.metro-sound - option.label value="bleep" bleep - option.label value="click" click - option.label value="drum" drum + select.metronome-select.metro-sound title="Metronome Sound" + option.label value="Beep" Bleep + option.label value="Click" Click + option.label value="Snare" Drum br - select.metronome-select.metro-tempo + select.metronome-select.metro-tempo title="Metronome Tempo" - metronome_tempos.each do |t| option.label value=t =t From b7b401cf5984014aad39db536a4923d683302d1e Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Thu, 12 Feb 2015 18:51:07 -0600 Subject: [PATCH 32/46] VRFS-2498 : Add metronome callback to fake client for tests. --- web/app/assets/javascripts/fakeJamClient.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index f4b1db377..0f22eaee2 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -758,6 +758,10 @@ metronomeActive=false } + function setMetronomeOpenCallback(callback) { + + } + // stun function NetworkTestResult() { return {remote_udp_blocked: false} } @@ -1057,14 +1061,17 @@ this.GetScoreWorkTimingInterval = GetScoreWorkTimingInterval; this.SetScoreWorkTimingInterval = SetScoreWorkTimingInterval; + // Backing tracks: this.getBackingTrackList = getBackingTrackList; this.SessionCloseBackingTrackFile = SessionCloseBackingTrackFile; this.SessionOpenBackingTrackFile = SessionOpenBackingTrackFile; this.SessionSetBackingTrackFileLoop = SessionSetBackingTrackFileLoop; + // Metronome: this.SessionCloseMetronome = SessionCloseMetronome; this.SessionOpenMetronome = SessionOpenMetronome; this.SessionSetMetronome = SessionSetMetronome; + this.setMetronomeOpenCallback = setMetronomeOpenCallback; // Client Update this.IsAppInWritableVolume = IsAppInWritableVolume; From bbce83984b21c572c2847f09056a0326eab72e82 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 13 Feb 2015 11:36:03 -0600 Subject: [PATCH 33/46] VRFS-2498 : Fix spec. --- web/app/assets/javascripts/fakeJamClient.js | 7 +------ web/app/assets/javascripts/session.js | 5 +++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 0f22eaee2..86e3103a3 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -415,9 +415,7 @@ "", "", "", - "", - "", - "" + "" ]; var media_types = [ @@ -442,9 +440,6 @@ "", "", "", - "", - "", - "", "" ]; var response = []; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index fff71eff5..3df41a575 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -851,15 +851,16 @@ function groupByType(mixers) { context._.each(mixers, function(mixer) { var mediaType = mixer.media_type; + var groupId = mixer.group_id; if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { // BackingTrack backingTrackMixers.push(mixer); - } else if(mediaType == 'MetronomeTrack' || mixer.group_id==ChannelGroupIds.MetronomeGroup) { + } else if(mediaType == 'MetronomeTrack' || groupId==ChannelGroupIds.MetronomeGroup) { // Metronomes come across with a blank media type, so check group_id: metronomeTrackMixers.push(mixer); } else if(mediaType == 'JamTrack') { jamTrackMixers.push(mixer); - group_id == ChannelGroupIds.MediaTrackGroup; + mixer.group_id == ChannelGroupIds.MediaTrackGroup; } else if(mediaType == null || mediaType == "" || mediaType == 'RecordingTrack') { // mediaType == null is for backwards compat with older clients. Can be removed soon recordingTrackMixers.push(mixer) From 5af190871c3d422c8c5b2255d224213d1a3f00d6 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 13 Feb 2015 19:54:18 +0000 Subject: [PATCH 34/46] bump --- web/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/web/README.md b/web/README.md index efb2ab112..b32f3ab99 100644 --- a/web/README.md +++ b/web/README.md @@ -1,6 +1,3 @@ -TODO: -==== - Jasmine Javascript Unit Tests ============================= From 93ecdd6c863c6249a2d45792e9e23f83cdc38700 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 13 Feb 2015 15:34:30 -0600 Subject: [PATCH 35/46] Fix test: verify on a non-ellipsised attribute, i.e., not text. --- web/app/views/clients/_session.html.slim | 2 +- web/spec/features/in_session_spec.rb | 2 +- web/spec/support/utilities.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index c765d4d61..452d88809 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -108,7 +108,7 @@ script#template-session-track[type="text/template"] .session-track.track client-id="{clientId}" track-id="{trackId}" .track-vu-left.mixer-id="{vuMixerId}_vul" .track-vu-right.mixer-id="{vuMixerId}_vur" - .track-label + .track-label[title="{name}"] span.name-text="{name}" #div-track-close.track-close.op30 track-id="{trackId}" =image_tag("content/icon_closetrack.png", {width: 12, height: 12}) diff --git a/web/spec/features/in_session_spec.rb b/web/spec/features/in_session_spec.rb index 424011e16..576b51bd0 100644 --- a/web/spec/features/in_session_spec.rb +++ b/web/spec/features/in_session_spec.rb @@ -59,7 +59,7 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr end leave_music_session_sleep_delay - in_client(finder) { expect(page).to_not have_selector('div.track-label > span', text: user.name) } + in_client(finder) { expect(page).to_not have_selector('div.track-label', title: user.name) } end many = 4 diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 8a5722f50..3f9afd7ff 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -637,7 +637,7 @@ def assert_all_tracks_seen(users=[]) users.each do |user| in_client(user) do users.reject {|u| u==user}.each do |other| - find('div.track-label > span', text: other.name) + find('div.track-label', title: other.name) #puts user.name + " is able to see " + other.name + "\'s track" end end From 40368deb8335a6fe462dd9b9a09f278d52e29018 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Fri, 13 Feb 2015 16:43:52 -0600 Subject: [PATCH 36/46] Fix test "for real". --- web/spec/features/in_session_spec.rb | 3 ++- web/spec/support/utilities.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/spec/features/in_session_spec.rb b/web/spec/features/in_session_spec.rb index 576b51bd0..693476818 100644 --- a/web/spec/features/in_session_spec.rb +++ b/web/spec/features/in_session_spec.rb @@ -59,7 +59,8 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr end leave_music_session_sleep_delay - in_client(finder) { expect(page).to_not have_selector('div.track-label', title: user.name) } + in_client(finder) { expect(page).to_not have_selector("div.track-label[title='#{user.name}']") } + end many = 4 diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index 3f9afd7ff..8982fd5a1 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -637,7 +637,7 @@ def assert_all_tracks_seen(users=[]) users.each do |user| in_client(user) do users.reject {|u| u==user}.each do |other| - find('div.track-label', title: other.name) + find("div.track-label[title='#{other.name}']") #puts user.name + " is able to see " + other.name + "\'s track" end end From c52c429e9690eac9a1d92a072f4c88692a49e495 Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 15 Feb 2015 14:00:36 -0600 Subject: [PATCH 37/46] VRFS-2473 : Check ntp clock stability of all session participants before opening metronome. --- web/app/assets/javascripts/fakeJamClient.js | 15 +++++ web/app/assets/javascripts/session.js | 62 +++++++++++++++++---- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 86e3103a3..c1c842ca6 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -757,6 +757,19 @@ } + function getMyNetworkState() { + return { + ntp_stable: Math.random() > 0.5 + } + } + + function getPeerState(clientId) { + return { + ntp_stable: Math.random() > 0.2 + } + } + + // stun function NetworkTestResult() { return {remote_udp_blocked: false} } @@ -1067,6 +1080,8 @@ this.SessionOpenMetronome = SessionOpenMetronome; this.SessionSetMetronome = SessionSetMetronome; this.setMetronomeOpenCallback = setMetronomeOpenCallback; + this.getMyNetworkState = getMyNetworkState; + this.getPeerState = getPeerState; // Client Update this.IsAppInWritableVolume = IsAppInWritableVolume; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 3df41a575..5de3c72b6 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -2189,8 +2189,39 @@ return false; } + function unstableNTPClocks() { + var unstable = [] + + // This should be handled in the below loop, actually: + // var map = context.jamClient.getMyNetworkState() + // if (!map.ntp_stable) { + // unstable.push("self"); + // } + + var map; + $.each(sessionModel.participants(), function(index, participant) { + map = context.jamClient.getPeerState(participant.client_id) + + if (!map.ntp_stable) { + var name = participant.user.name; + if (!(name)) { + name = participant.user.first_name + ' ' + participant.user.last_name; + } + + if (app.clientId == participant.client_id) { + name += " (This computer)" + } + + unstable.push(name) + } + }); + + return unstable + } + function openMetronome(e) { // just ignore the click if they are currently recording for now + if(sessionModel.recordingModel.isRecording()) { app.notify({ "title": "Currently Recording", @@ -2199,20 +2230,31 @@ }); return false; } else { - rest.openMetronome({id: sessionModel.id()}) - .done(function() { - context.jamClient.SessionOpenMetronome(120, "Click", 1, 0) - context.JK.CurrentSessionModel.refreshCurrentSession(true) - context.JK.CurrentSessionModel.refreshCurrentSession(true) - }) - .fail(function(jqXHR) { - console.log(jqXHR, jqXHR) + var unstable = unstableNTPClocks() + if (unstable.length > 0) { + var names = unstable.join(", ") + console.log("Unstable clocks: ", names, unstable) app.notify({ "title": "Couldn't open metronome", - "text": "Couldn't inform open metronome. msg=" + jqXHR.responseText, + "text": "The metronome feature requires that every user's computer in the session must agree on the current time. The computers of " + names + " have not successfully synchronized to the current time. The JamKazam service is trying to automatically correct this error condition. Please close this message, wait about 10 seconds, and then try opening the metronome again. If this problem persists after a couple of attempts, we recommend that the unsynchronized users restart the JamKazam application. If this error persists after a restart, please have the users with the issue contact support@jamkazam.com.", "icon_url": "/assets/content/icon_alert_big.png" }); - }); + } else { + rest.openMetronome({id: sessionModel.id()}) + .done(function() { + context.jamClient.SessionOpenMetronome(120, "Click", 1, 0) + context.JK.CurrentSessionModel.refreshCurrentSession(true) + context.JK.CurrentSessionModel.refreshCurrentSession(true) + }) + .fail(function(jqXHR) { + console.log(jqXHR, jqXHR) + app.notify({ + "title": "Couldn't open metronome", + "text": "Couldn't inform the server to open metronome. msg=" + jqXHR.responseText, + "icon_url": "/assets/content/icon_alert_big.png" + }); + }); + } return false; From 8a737b35564464836680c4085735088bacfc51cd Mon Sep 17 00:00:00 2001 From: Steven Miers Date: Sun, 15 Feb 2015 14:44:38 -0600 Subject: [PATCH 38/46] VRFS-2473 : Add metronome feature tests. --- web/app/assets/javascripts/fakeJamClient.js | 2 +- web/spec/features/in_session_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index c1c842ca6..5547bc111 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -765,7 +765,7 @@ function getPeerState(clientId) { return { - ntp_stable: Math.random() > 0.2 + ntp_stable: Math.random() > 0.5 } } diff --git a/web/spec/features/in_session_spec.rb b/web/spec/features/in_session_spec.rb index 693476818..b9b5b7296 100644 --- a/web/spec/features/in_session_spec.rb +++ b/web/spec/features/in_session_spec.rb @@ -111,6 +111,26 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr end end + specify "metronome" do + user = FactoryGirl.create(:user) + invitee = FactoryGirl.create(:user) + FactoryGirl.create(:friendship, :user => user, :friend => invitee) + in_client(user) do + create_session + + # Call it 10 times. The fake jam client will randomly ntp_stable:false 50% of the time + 10.times do + find('#open-a-metronome').trigger(:click) + end + sleep(2) + + # we should have received an error one of the times, but it should open eventually: + find('#notification').should have_text("Couldn't open metronome") + expect(page).to have_selector('.track-label[title="Metronome"]') + #save_screenshot("metro.png") + end + end + specify "invitee receives notification when creator invites musician" do pending "blocked on testing this via front-end - fakeJamClient doesn't support invite UX" user = FactoryGirl.create(:user) From c4c05c67d68e1fc885ff9a9168e5b1fc24cbea65 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 2 Feb 2015 11:39:28 -0600 Subject: [PATCH 39/46] * working on sync rework VRFS-2661 --- web/app/assets/javascripts/sessionModel.js | 68 ++++++++++++---------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 890231ded..0b5d9e52f 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -557,6 +557,40 @@ return mixerMode; } + function syncTracks() { + // double check that we are in session, since a bunch could have happened since then + if(!inSession()) { + logger.debug("dropping queued up sync tracks because no longer in session"); + return; + } + + // this is a local change to our tracks. we need to tell the server about our updated track information + var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); + + // create a trackSync request based on backend data + var syncTrackRequest = {}; + syncTrackRequest.client_id = app.clientId; + syncTrackRequest.tracks = inputTracks; + syncTrackRequest.id = id(); + + rest.putTrackSyncChange(syncTrackRequest) + .done(function() { + }) + .fail(function(jqXHR) { + if(jqXHR.status != 404) { + app.notify({ + "title": "Can't Sync Local Tracks", + "text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + } + else { + logger.debug("Unable to sync local tracks because session is gone.") + } + + }) + } + function onWebsocketDisconnected(in_error) { // kill the streaming of the session immediately if(currentSessionId) { @@ -700,43 +734,15 @@ if(joinDeferred) { joinDeferred.done(function() { - // double check that we are in session, since a bunch could have happened since then - if(!inSession()) { - logger.debug("dropping queued up sync tracks because no longer in session"); - return; - } - - // this is a local change to our tracks. we need to tell the server about our updated track information - var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); - - // create a trackSync request based on backend data - var syncTrackRequest = {}; - syncTrackRequest.client_id = app.clientId; - syncTrackRequest.tracks = inputTracks; - syncTrackRequest.id = id(); - - rest.putTrackSyncChange(syncTrackRequest) - .done(function() { - }) - .fail(function(jqXHR) { - if(jqXHR.status != 404) { - app.notify({ - "title": "Can't Sync Local Tracks", - "text": "The client is unable to sync local track information with the server. You should rejoin the session to ensure a good experience.", - "icon_url": "/assets/content/icon_alert_big.png" - }); - } - else { - logger.debug("Unable to sync local tracks because session is gone.") - } - - }) + syncTracks(); }) } }, 100); } else if(inSession() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) { + + // refreshCurrentSession(true); } else if(inSession() && (text == 'Global Peer Input Mixer Mode')) { From 503e46ed74cd45fea3d11a18170c3bb136e16e3d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 3 Feb 2015 09:25:45 -0600 Subject: [PATCH 40/46] *wip --- db/up/recorded_backing_tracks.sql | 6 +-- .../dialog/openBackingTrackDialog.js | 5 ++- web/app/assets/javascripts/session.js | 17 ++++++--- web/app/assets/javascripts/sessionModel.js | 38 ++++++++++++++++--- web/app/assets/javascripts/trackHelpers.js | 16 ++++++++ 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/db/up/recorded_backing_tracks.sql b/db/up/recorded_backing_tracks.sql index 2b59895a0..6657dee1a 100644 --- a/db/up/recorded_backing_tracks.sql +++ b/db/up/recorded_backing_tracks.sql @@ -1,8 +1,8 @@ -CREATE TABLE backing_tracks ( +CREATE UNLOGGED TABLE backing_tracks ( id VARCHAR(64) NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(), filename VARCHAR(1024) NOT NULL, - connection_id VARCHAR(64) NOT NULL, + connection_id VARCHAR(64) NOT NULL REFERENCES connections(id) ON DELETE CASCADE, client_track_id VARCHAR(64) NOT NULL, client_resource_id VARCHAR(100), @@ -13,7 +13,7 @@ CREATE TABLE backing_tracks ( CREATE TABLE recorded_backing_tracks ( id BIGINT PRIMARY KEY, user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE, - backing_track_id VARCHAR(64) REFERENCES backing_tracks(id) ON DELETE CASCADE, + backing_track_id VARCHAR(64), recording_id VARCHAR(64) NOT NULL, client_track_id VARCHAR(64) NOT NULL, diff --git a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js index 82038a760..87150456f 100644 --- a/web/app/assets/javascripts/dialog/openBackingTrackDialog.js +++ b/web/app/assets/javascripts/dialog/openBackingTrackDialog.js @@ -28,7 +28,8 @@ emptyList(); resetPagination(); showing = true; - getBackingTracks() + getBackingTracks(); + $dialog.data('result', null); // .done(function(data, textStatus, jqXHR) { // // initialize pagination // var $paginator = context.JK.Paginator.create(parseInt(jqXHR.getResponseHeader('total-entries')), perPage, 0, onPageSelected) @@ -93,6 +94,8 @@ // TODO: Possibly actually check the result. Investigate // what real client returns: // // if(result) { + // let callers see which backing track was chosen + $dialog.data('result', backingTrack); app.layout.closeDialog('open-backing-track-dialog'); // } // else { diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 5de3c72b6..85e400115 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -2152,8 +2152,11 @@ return false; } - app.layout.showDialog('open-backing-track-dialog'); - + app.layout.showDialog('open-backing-track-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { + if(!data.cancel && data.result){ + sessionModel.setBackingTrack(data.result); + } + }) return false; } @@ -2184,7 +2187,7 @@ return false; } else { context.jamClient.openBackingTrackFile(sessionModel.backing_track) - context.JK.CurrentSessionModel.refreshCurrentSession(true); + //context.JK.CurrentSessionModel.refreshCurrentSession(true); } return false; } @@ -2294,14 +2297,15 @@ closeMetronomeTrack(); } else { - logger.error("don't know how to close open media (backing track?)"); + logger.error("don't know how to close open media"); } + return false; } function closeBackingTrack() { rest.closeBackingTrack({id: sessionModel.id()}) .done(function() { - sessionModel.refreshCurrentSession(true); + //sessionModel.refreshCurrentSession(true); }) .fail(function(jqXHR) { app.notify({ @@ -2311,7 +2315,8 @@ }); }); - context.jamClient.SessionCloseBackingTrackFile(); + // '' closes all open backing tracks + context.jamClient.SessionCloseBackingTrackFile(''); return false; } diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 0b5d9e52f..7f2ce0776 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -34,6 +34,8 @@ var sessionPageEnterTimeout = null; var startTime = null; var joinDeferred = null; + var previousBackingTracks = []; + var openBackingTrack = null; var mixerMode = MIX_MODES.PERSONAL; @@ -324,6 +326,8 @@ } currentSessionId = null; currentParticipants = {} + previousBackingTracks = [] + openBackingTrack = null } // you should only update currentSession with this function @@ -557,23 +561,31 @@ return mixerMode; } - function syncTracks() { + function syncTracks(backingTracks) { // double check that we are in session, since a bunch could have happened since then if(!inSession()) { logger.debug("dropping queued up sync tracks because no longer in session"); - return; + return null; } // this is a local change to our tracks. we need to tell the server about our updated track information var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); + // backingTracks can be passed in as an optimization, so that we don't hit the backend excessively + if(backingTracks === undefined) { + backingTracks = context.JK.TrackHelpers.getBackingTracks(context.jamClient); + } + + console.log('backingTracks', backingTracks); + // create a trackSync request based on backend data var syncTrackRequest = {}; syncTrackRequest.client_id = app.clientId; syncTrackRequest.tracks = inputTracks; + syncTrackRequest.backing_tracks = backingTracks; syncTrackRequest.id = id(); - rest.putTrackSyncChange(syncTrackRequest) + return rest.putTrackSyncChange(syncTrackRequest) .done(function() { }) .fail(function(jqXHR) { @@ -733,7 +745,6 @@ // wait until we are fully in session before trying to sync tracks to server if(joinDeferred) { joinDeferred.done(function() { - syncTracks(); }) } @@ -742,8 +753,17 @@ } else if(inSession() && (text == 'RebuildMediaControl' || text == 'RebuildRemoteUserControl')) { - // - refreshCurrentSession(true); + var backingTracks = context.JK.TrackHelpers.getBackingTracks(context.jamClient); + + // the way we know if backing tracks changes, or recordings are opened, is via this event. + // but we want to report to the user when backing tracks change; so we need to detect change on our own + if(previousBackingTracks != backingTracks) { + logger.debug("backing tracks changed") + syncTracks(backingTracks); + } + else { + refreshCurrentSession(true); + } } else if(inSession() && (text == 'Global Peer Input Mixer Mode')) { setMixerMode(MIX_MODES.MASTER); @@ -796,6 +816,12 @@ this.getParticipant = function(clientId) { return participantsEverSeen[clientId] }; + this.setBackingTrack = function(backingTrack) { + openBackingTrack = backingTrack; + }; + this.getBackingTrack = function() { + return openBackingTrack; + }; // call to report if the current user was able to establish audio with the specified clientID this.setAudioEstablished = function(clientId, audioEstablished) { diff --git a/web/app/assets/javascripts/trackHelpers.js b/web/app/assets/javascripts/trackHelpers.js index 6b30035e0..f75ba85a3 100644 --- a/web/app/assets/javascripts/trackHelpers.js +++ b/web/app/assets/javascripts/trackHelpers.js @@ -30,6 +30,22 @@ return tracks; }, + getBackingTracks: function(jamClient) { + var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 4); + + + var backingTracks = [] + context._.each(mediaTracks, function(mediaTrack) { + var track = {}; + track.client_track_id = mediaTrack.id; + track.client_resource_id = mediaTrack.rid; + track.filename = mediaTrack.id; + backingTracks.push(track); + }) + + return backingTracks; + }, + /** * This function resolves which tracks to configure for a user * when creating or joining a session. By default, tracks are pulled From 42a2abe99ccbdef5322f4bb4fd83a984855ecffc Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 15 Feb 2015 22:01:06 -0600 Subject: [PATCH 41/46] * merged --- db/manifest | 1 + .../recorded_backing_tracks_add_filename.sql | 3 +- db/up/user_syncs_include_backing_tracks.sql | 47 ++++ ruby/lib/jam_ruby.rb | 1 + ruby/lib/jam_ruby/models/mix.rb | 6 + .../jam_ruby/models/recorded_backing_track.rb | 161 ++++++++++++- .../models/recorded_backing_track_observer.rb | 91 +++++++ ruby/lib/jam_ruby/models/recorded_track.rb | 2 +- ruby/lib/jam_ruby/models/recording.rb | 6 +- ruby/lib/jam_ruby/models/user_sync.rb | 3 +- ruby/spec/factories.rb | 24 +- .../models/recorded_backing_track_spec.rb | 228 ++++++++++++++++++ ruby/spec/jam_ruby/models/user_sync_spec.rb | 43 ++++ ruby/spec/spec_helper.rb | 1 + .../dialog/localRecordingsDialog.js | 4 + web/app/assets/javascripts/session.js | 112 +++++---- web/app/assets/javascripts/sessionModel.js | 65 +++-- .../assets/javascripts/sync_viewer.js.coffee | 207 +++++++++++++++- web/app/assets/javascripts/trackHelpers.js | 16 +- .../assets/stylesheets/client/help.css.scss | 2 +- .../controllers/api_recordings_controller.rb | 78 +++++- .../views/api_claimed_recordings/show.rabl | 14 ++ web/app/views/api_music_sessions/show.rabl | 12 + web/app/views/api_recordings/show.rabl | 6 + .../show_recorded_backing_track.rabl | 7 + web/app/views/api_user_syncs/show.rabl | 40 ++- .../clients/_sync_viewer_templates.html.slim | 53 ++++ web/config/application.rb | 2 +- web/config/routes.rb | 11 +- .../api_recordings_controller_spec.rb | 61 ++++- web/spec/factories.rb | 18 ++ web/spec/spec_helper.rb | 6 + 32 files changed, 1245 insertions(+), 86 deletions(-) create mode 100644 db/up/user_syncs_include_backing_tracks.sql create mode 100644 ruby/lib/jam_ruby/models/recorded_backing_track_observer.rb create mode 100644 ruby/spec/jam_ruby/models/recorded_backing_track_spec.rb create mode 100644 web/app/views/api_recordings/show_recorded_backing_track.rabl diff --git a/db/manifest b/db/manifest index 6483cb000..0e81a7c5f 100755 --- a/db/manifest +++ b/db/manifest @@ -251,3 +251,4 @@ performance_samples.sql user_presences.sql recorded_backing_tracks_add_filename.sql discard_scores_optimized.sql +user_syncs_include_backing_tracks.sql \ No newline at end of file diff --git a/db/up/recorded_backing_tracks_add_filename.sql b/db/up/recorded_backing_tracks_add_filename.sql index af959b9d5..e686700b7 100644 --- a/db/up/recorded_backing_tracks_add_filename.sql +++ b/db/up/recorded_backing_tracks_add_filename.sql @@ -1 +1,2 @@ -ALTER TABLE recorded_backing_tracks ADD COLUMN filename VARCHAR NOT NULL; \ No newline at end of file +ALTER TABLE recorded_backing_tracks ADD COLUMN filename VARCHAR NOT NULL; +ALTER TABLE recorded_backing_tracks ADD COLUMN last_downloaded_at TIMESTAMP WITHOUT TIME ZONE; \ No newline at end of file diff --git a/db/up/user_syncs_include_backing_tracks.sql b/db/up/user_syncs_include_backing_tracks.sql new file mode 100644 index 000000000..165617abf --- /dev/null +++ b/db/up/user_syncs_include_backing_tracks.sql @@ -0,0 +1,47 @@ + +DROP VIEW user_syncs; + +CREATE VIEW user_syncs AS + SELECT DISTINCT b.id AS recorded_track_id, + CAST(NULL as BIGINT) AS mix_id, + CAST(NULL as BIGINT) AS quick_mix_id, + CAST(NULL as BIGINT) AS recorded_backing_track_id, + b.id AS unified_id, + a.user_id AS user_id, + b.fully_uploaded, + recordings.created_at AS created_at, + recordings.id AS recording_id + FROM recorded_tracks a INNER JOIN recordings ON a.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE INNER JOIN recorded_tracks b ON a.recording_id = b.recording_id + UNION ALL + SELECT CAST(NULL AS BIGINT) AS recorded_track_id, + CAST(NULL as BIGINT) AS mix_id, + CAST(NULL as BIGINT) AS quick_mix_id, + a.id AS recorded_backing_track_id, + a.id AS unified_id, + a.user_id AS user_id, + a.fully_uploaded, + recordings.created_at AS created_at, + recordings.id AS recording_id + FROM recorded_backing_tracks a INNER JOIN recordings ON a.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE + UNION ALL + SELECT CAST(NULL as BIGINT) AS recorded_track_id, + mixes.id AS mix_id, + CAST(NULL as BIGINT) AS quick_mix_id, + CAST(NULL as BIGINT) AS recorded_backing_track_id, + mixes.id AS unified_id, + claimed_recordings.user_id AS user_id, + NULL as fully_uploaded, + recordings.created_at AS created_at, + recordings.id AS recording_id + FROM mixes INNER JOIN recordings ON mixes.recording_id = recordings.id INNER JOIN claimed_recordings ON recordings.id = claimed_recordings.recording_id WHERE claimed_recordings.discarded = FALSE AND deleted = FALSE + UNION ALL + SELECT CAST(NULL as BIGINT) AS recorded_track_id, + CAST(NULL as BIGINT) AS mix_id, + quick_mixes.id AS quick_mix_id, + CAST(NULL as BIGINT) AS recorded_backing_track_id, + quick_mixes.id AS unified_id, + quick_mixes.user_id, + quick_mixes.fully_uploaded, + recordings.created_at AS created_at, + recordings.id AS recording_id + FROM quick_mixes INNER JOIN recordings ON quick_mixes.recording_id = recordings.id AND duration IS NOT NULL AND all_discarded = FALSE AND deleted = FALSE; diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index 0f14d015d..b94d7d9a2 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -134,6 +134,7 @@ require "jam_ruby/models/recording" require "jam_ruby/models/recording_comment" require "jam_ruby/models/recording_liker" require "jam_ruby/models/recorded_backing_track" +require "jam_ruby/models/recorded_backing_track_observer" require "jam_ruby/models/recorded_track" require "jam_ruby/models/recorded_track_observer" require "jam_ruby/models/recorded_video" diff --git a/ruby/lib/jam_ruby/models/mix.rb b/ruby/lib/jam_ruby/models/mix.rb index 8e8ab2ace..83557f4f7 100644 --- a/ruby/lib/jam_ruby/models/mix.rb +++ b/ruby/lib/jam_ruby/models/mix.rb @@ -138,11 +138,17 @@ module JamRuby manifest = { "files" => [], "timeline" => [] } mix_params = [] + recording.recorded_tracks.each do |recorded_track| manifest["files"] << { "filename" => recorded_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 } mix_params << { "level" => 100, "balance" => 0 } end + recording.recorded_backing_tracks.each do |recorded_backing_track| + manifest["files"] << { "filename" => recorded_backing_track.sign_url(one_day), "codec" => "vorbis", "offset" => 0 } + mix_params << { "level" => 100, "balance" => 0 } + end + manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params } manifest["output"] = { "codec" => "vorbis" } manifest["recording_id"] = self.recording.id diff --git a/ruby/lib/jam_ruby/models/recorded_backing_track.rb b/ruby/lib/jam_ruby/models/recorded_backing_track.rb index 4d8d5ff35..f37659d3e 100644 --- a/ruby/lib/jam_ruby/models/recorded_backing_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_backing_track.rb @@ -1,10 +1,30 @@ module JamRuby # BackingTrack analog to JamRuby::RecordedTrack - class RecordedBackingTrack < ActiveRecord::Base + class RecordedBackingTrack < ActiveRecord::Base + + include JamRuby::S3ManagerMixin + + attr_accessor :marking_complete + attr_writer :current_user + belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_backing_tracks belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_backing_tracks validates :filename, :presence => true + validates :client_id, :presence => true # not a connection relation on purpose + validates :backing_track_id, :presence => true # not a track relation on purpose + validates :client_track_id, :presence => true + validates :md5, :presence => true, :if => :upload_starting? + validates :length, length: {minimum: 1, maximum: 1024 * 1024 * 256 }, if: :upload_starting? # 256 megs max. is this reasonable? surely... + validates :user, presence: true + validates :download_count, presence: true + + before_destroy :delete_s3_files + validate :validate_fully_uploaded + validate :validate_part_complete + validate :validate_too_many_upload_failures + validate :verify_download_count + def self.create_from_backing_track(backing_track, recording) recorded_backing_track = self.new recorded_backing_track.recording = recording @@ -20,6 +40,145 @@ module JamRuby recorded_backing_track end + def sign_url(expiration_time = 120) + s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => false}) + end + + def can_download?(some_user) + claimed_recording = recording.claimed_recordings.find{|claimed_recording| claimed_recording.user == some_user } + + if claimed_recording + !claimed_recording.discarded + else + false + end + end + + def too_many_upload_failures? + upload_failures >= APP_CONFIG.max_track_upload_failures + end + + def too_many_downloads? + (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin + end + + def upload_starting? + next_part_to_upload_was == 0 && next_part_to_upload == 1 + end + + def validate_too_many_upload_failures + if upload_failures >= APP_CONFIG.max_track_upload_failures + errors.add(:upload_failures, ValidationMessages::UPLOAD_FAILURES_EXCEEDED) + end + end + + def validate_fully_uploaded + if marking_complete && fully_uploaded && fully_uploaded_was + errors.add(:fully_uploaded, ValidationMessages::ALREADY_UPLOADED) + end + end + + def validate_part_complete + + # if we see a transition from is_part_uploading from true to false, we validate + if is_part_uploading_was && !is_part_uploading + if next_part_to_upload_was + 1 != next_part_to_upload + errors.add(:next_part_to_upload, ValidationMessages::INVALID_PART_NUMBER_SPECIFIED) + end + + if file_offset > length + errors.add(:file_offset, ValidationMessages::FILE_OFFSET_EXCEEDS_LENGTH) + end + elsif next_part_to_upload_was + 1 == next_part_to_upload + # this makes sure we are only catching 'upload_part_complete' transitions, and not upload_start + if next_part_to_upload_was != 0 + # we see that the part number was ticked--but was is_part_upload set to true before this transition? + if !is_part_uploading_was && !is_part_uploading + errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_STARTED) + end + end + end + end + + def verify_download_count + if (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin + errors.add(:download_count, "must be less than or equal to 100") + end + end + + + def upload_start(length, md5) + #self.upload_id set by the observer + self.next_part_to_upload = 1 + self.length = length + self.md5 = md5 + save + end + + # if for some reason the server thinks the client can't carry on with the upload, + # this resets everything to the initial state + def reset_upload + self.upload_failures = self.upload_failures + 1 + self.part_failures = 0 + self.file_offset = 0 + self.next_part_to_upload = 0 + self.upload_id = nil + self.md5 = nil + self.length = 0 + self.fully_uploaded = false + self.is_part_uploading = false + save :validate => false # skip validation because we need this to always work + end + + def upload_next_part(length, md5) + self.marking_complete = true + if next_part_to_upload == 0 + upload_start(length, md5) + end + self.is_part_uploading = true + save + end + + def upload_sign(content_md5) + s3_manager.upload_sign(self[:url], content_md5, next_part_to_upload, upload_id) + end + + def upload_part_complete(part, offset) + # validated by :validate_part_complete + self.marking_complete = true + self.is_part_uploading = false + self.next_part_to_upload = self.next_part_to_upload + 1 + self.file_offset = offset.to_i + self.part_failures = 0 + save + end + + def upload_complete + # validate from happening twice by :validate_fully_uploaded + self.fully_uploaded = true + self.marking_complete = true + save + end + + def increment_part_failures(part_failure_before_error) + self.part_failures = part_failure_before_error + 1 + RecordedBackingTrack.update_all("part_failures = #{self.part_failures}", "id = '#{self.id}'") + end + + def stored_filename + # construct a path from s3 + RecordedBacknigTrack.construct_filename(recording.created_at, self.recording.id, self.client_track_id) + end + + def update_download_count(count=1) + self.download_count = self.download_count + count + self.last_downloaded_at = Time.now + end + + def delete_s3_files + s3_manager.delete(self[:url]) if self[:url] && s3_manager.exists?(self[:url]) + end + private def self.construct_filename(created_at, recording_id, client_track_id) diff --git a/ruby/lib/jam_ruby/models/recorded_backing_track_observer.rb b/ruby/lib/jam_ruby/models/recorded_backing_track_observer.rb new file mode 100644 index 000000000..1a2b8f291 --- /dev/null +++ b/ruby/lib/jam_ruby/models/recorded_backing_track_observer.rb @@ -0,0 +1,91 @@ +module JamRuby + class RecordedBackingTrackObserver < ActiveRecord::Observer + + # if you change the this class, tests really should accompany. having alot of logic in observers is really tricky, as we do here + observe JamRuby::RecordedBackingTrack + + def before_validation(recorded_backing_tracks) + + # if we see that a part was just uploaded entirely, validate that we can find the part that was just uploaded + if recorded_backing_tracks.is_part_uploading_was && !recorded_backing_tracks.is_part_uploading + begin + aws_part = recorded_backing_tracks.s3_manager.multiple_upload_find_part(recorded_backing_tracks[:url], recorded_backing_tracks.upload_id, recorded_backing_tracks.next_part_to_upload - 1) + # calling size on a part that does not exist will throw an exception... that's what we want + aws_part.size + rescue SocketError => e + raise # this should cause a 500 error, which is what we want. The client will retry later on 500. + rescue Exception => e + recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS) + rescue RuntimeError => e + recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS) + rescue + recorded_backing_tracks.errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_FOUND_IN_AWS) + end + + end + + # if we detect that this just became fully uploaded -- if so, tell s3 to put the parts together + if recorded_backing_tracks.marking_complete && !recorded_backing_tracks.fully_uploaded_was && recorded_backing_tracks.fully_uploaded + + multipart_success = false + begin + recorded_backing_tracks.s3_manager.multipart_upload_complete(recorded_backing_tracks[:url], recorded_backing_tracks.upload_id) + multipart_success = true + rescue SocketError => e + raise # this should cause a 500 error, which is what we want. The client will retry later. + rescue Exception => e + #recorded_track.reload + recorded_backing_tracks.reset_upload + recorded_backing_tracks.errors.add(:upload_id, ValidationMessages::BAD_UPLOAD) + end + + # unlike RecordedTracks, only the person who uploaded can download it, so no need to notify + + # tell all users that a download is available, except for the user who just uploaded + # recorded_backing_tracks.recording.users.each do |user| + #Notification.send_download_available(recorded_backing_tracks.user_id) unless user == recorded_backing_tracks.user + # end + + end + end + + def after_commit(recorded_backing_track) + + end + + # here we tick upload failure counts, or revert the state of the model, as needed + def after_rollback(recorded_backing_track) + # if fully uploaded, don't increment failures + if recorded_backing_track.fully_uploaded + return + end + + # increment part failures if there is a part currently being uploaded + if recorded_backing_track.is_part_uploading_was + #recorded_track.reload # we don't want anything else that the user set to get applied + recorded_backing_track.increment_part_failures(recorded_backing_track.part_failures_was) + if recorded_backing_track.part_failures >= APP_CONFIG.max_track_part_upload_failures + # save upload id before we abort this bad boy + upload_id = recorded_backing_track.upload_id + begin + recorded_backing_track.s3_manager.multipart_upload_abort(recorded_backing_track[:url], upload_id) + rescue => e + puts e.inspect + end + recorded_backing_track.reset_upload + if recorded_backing_track.upload_failures >= APP_CONFIG.max_track_upload_failures + # do anything? + end + end + end + + end + + def before_save(recorded_backing_track) + # if we are on the 1st part, then we need to make sure we can save the upload_id + if recorded_backing_track.next_part_to_upload == 1 + recorded_backing_track.upload_id = recorded_backing_track.s3_manager.multipart_upload_start(recorded_backing_track[:url]) + end + end + end +end \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/recorded_track.rb b/ruby/lib/jam_ruby/models/recorded_track.rb index e11a76bf0..2a6a828d1 100644 --- a/ruby/lib/jam_ruby/models/recorded_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_track.rb @@ -228,7 +228,7 @@ module JamRuby def self.construct_filename(created_at, recording_id, client_track_id) raise "unknown ID" unless client_track_id - "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/track-#{client_track_id}.ogg" + "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/backing-track-#{client_track_id}.ogg" end end end diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index cf1597ef3..52e4643af 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -363,6 +363,7 @@ module JamRuby .order('recorded_backing_tracks.id') .where('recorded_backing_tracks.fully_uploaded = TRUE') .where('recorded_backing_tracks.id > ?', since) + .where('recorded_backing_tracks.user_id = ?', user.id) # only the person who opened the backing track can have it back .where('all_discarded = false') .where('deleted = false') .where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_backing_track| @@ -453,7 +454,7 @@ module JamRuby :url, :fully_uploaded, :upload_failures, - Arel::Nodes::As.new('', Arel.sql('backing_track_track_id')), + :client_track_id, Arel::Nodes::As.new('backing_track', Arel.sql('item_type')) ]).reorder("") @@ -551,8 +552,9 @@ module JamRuby }) elsif recorded_item.item_type == 'backing_track' uploads << ({ - :type => "backing_track", + :type => "recorded_backing_track", :recording_id => recorded_item.recording_id, + :client_track_id => recorded_item.client_track_id, :next => recorded_item.id }) else diff --git a/ruby/lib/jam_ruby/models/user_sync.rb b/ruby/lib/jam_ruby/models/user_sync.rb index 8cdeac13b..1e915b953 100644 --- a/ruby/lib/jam_ruby/models/user_sync.rb +++ b/ruby/lib/jam_ruby/models/user_sync.rb @@ -4,6 +4,7 @@ module JamRuby belongs_to :recorded_track belongs_to :mix belongs_to :quick_mix + belongs_to :recorded_backing_track def self.show(id, user_id) self.index({user_id: user_id, id: id, limit: 1, offset: 0})[:query].first @@ -22,7 +23,7 @@ module JamRuby raise 'no user id specified' if user_id.blank? query = UserSync - .includes(recorded_track: [{recording: [:owner, {claimed_recordings: [:share_token]}, {recorded_tracks: [:user]}, {comments:[:user]}, :likes, :plays, :mixes]}, user: [], instrument:[]], mix: [], quick_mix:[]) + .includes(recorded_track: [{recording: [:owner, {claimed_recordings: [:share_token]}, {recorded_tracks: [:user]}, {comments:[:user]}, :likes, :plays, :mixes]}, user: [], instrument:[]], mix: [], quick_mix:[], recorded_backing_track:[]) .joins("LEFT OUTER JOIN claimed_recordings ON claimed_recordings.user_id = user_syncs.user_id AND claimed_recordings.recording_id = user_syncs.recording_id") .where(user_id: user_id) .where(%Q{ diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index 717ff789e..8d897d4ac 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -232,16 +232,16 @@ FactoryGirl.define do sequence(:client_resource_id) { |n| "resource_id#{n}"} end + factory :backing_track, :class => JamRuby::BackingTrack do + sequence(:client_track_id) { |n| "client_track_id#{n}"} + filename 'foo.mp3' + end + factory :video_source, :class => JamRuby::VideoSource do #client_video_source_id "test_source_id" sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"} end - factory :backing_track, :class => JamRuby::BackingTrack do - sequence(:client_track_id) { |n| "client_track_id#{n}"} - filename 'foo.mp3' - end - factory :recorded_track, :class => JamRuby::RecordedTrack do instrument JamRuby::Instrument.first sound 'stereo' @@ -255,6 +255,20 @@ FactoryGirl.define do association :recording, factory: :recording end + factory :recorded_backing_track, :class => JamRuby::RecordedBackingTrack do + sequence(:client_id) { |n| "client_id-#{n}"} + sequence(:backing_track_id) { |n| "track_id-#{n}"} + sequence(:client_track_id) { |n| "client_track_id-#{n}"} + sequence(:filename) { |n| "filename-{#n}"} + sequence(:url) { |n| "/recordings/blah/#{n}"} + md5 'abc' + length 1 + fully_uploaded true + association :user, factory: :user + association :recording, factory: :recording + end + + factory :recorded_video, :class => JamRuby::RecordedVideo do sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"} fully_uploaded true diff --git a/ruby/spec/jam_ruby/models/recorded_backing_track_spec.rb b/ruby/spec/jam_ruby/models/recorded_backing_track_spec.rb new file mode 100644 index 000000000..de65e22e1 --- /dev/null +++ b/ruby/spec/jam_ruby/models/recorded_backing_track_spec.rb @@ -0,0 +1,228 @@ +require 'spec_helper' +require 'rest-client' + +describe RecordedBackingTrack do + + include UsesTempFiles + + before do + @user = FactoryGirl.create(:user) + @connection = FactoryGirl.create(:connection, :user => @user) + @instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') + @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) + @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) + @backing_track = FactoryGirl.create(:backing_track, :connection => @connection) + @recording = FactoryGirl.create(:recording, :music_session => @music_session, :owner => @user) + end + + it "should copy from a regular track properly" do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + + @recorded_backing_track.user.id.should == @backing_track.connection.user.id + @recorded_backing_track.filename.should == @backing_track.filename + @recorded_backing_track.next_part_to_upload.should == 0 + @recorded_backing_track.fully_uploaded.should == false + @recorded_backing_track.client_id = @connection.client_id + @recorded_backing_track.backing_track_id = @backing_track.id + end + + it "should update the next part to upload properly" do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.upload_part_complete(1, 1000) + @recorded_backing_track.errors.any?.should be_true + @recorded_backing_track.errors[:length][0].should == "is too short (minimum is 1 characters)" + @recorded_backing_track.errors[:md5][0].should == "can't be blank" + end + + it "properly finds a recorded track given its upload filename" do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.save.should be_true + RecordedBackingTrack.find_by_recording_id_and_backing_track_id(@recorded_backing_track.recording_id, @recorded_backing_track.backing_track_id).should == @recorded_backing_track + end + + it "gets a url for the track" do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.errors.any?.should be_false + @recorded_backing_track[:url].should == "recordings/#{@recorded_backing_track.created_at.strftime('%m-%d-%Y')}/#{@recording.id}/backing-track-#{@backing_track.client_track_id}.ogg" + end + + it "signs url" do + stub_const("APP_CONFIG", app_config) + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.sign_url.should_not be_nil + end + + it "can not be downloaded if no claimed recording" do + user2 = FactoryGirl.create(:user) + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.can_download?(user2).should be_false + @recorded_backing_track.can_download?(@user).should be_false + end + + it "can be downloaded if there is a claimed recording" do + @recorded_track = RecordedTrack.create_from_track(@track, @recording) + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recording.claim(@user, "my recording", "my description", Genre.first, true).errors.any?.should be_false + @recorded_backing_track.can_download?(@user).should be_true + end + + + describe "aws-based operations", :aws => true do + + def put_file_to_aws(signed_data, contents) + + begin + RestClient.put( signed_data[:url], + contents, + { + :'Content-Type' => 'audio/ogg', + :Date => signed_data[:datetime], + :'Content-MD5' => signed_data[:md5], + :Authorization => signed_data[:authorization] + }) + rescue => e + puts e.response + raise e + end + + end + # create a test file + upload_file='some_file.ogg' + in_directory_with_file(upload_file) + + upload_file_contents="ogg binary stuff in here" + md5 = Base64.encode64(Digest::MD5.digest(upload_file_contents)).chomp + test_config = app_config + s3_manager = S3Manager.new(test_config.aws_bucket, test_config.aws_access_key_id, test_config.aws_secret_access_key) + + + before do + stub_const("APP_CONFIG", app_config) + # this block of code will fully upload a sample file to s3 + content_for_file(upload_file_contents) + s3_manager.delete_folder('recordings') # keep the bucket clean to save cost, and make it easier if post-mortuem debugging + + + end + + it "cant mark a part complete without having started it" do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.upload_start(1000, "abc") + @recorded_backing_track.upload_part_complete(1, 1000) + @recorded_backing_track.errors.any?.should be_true + @recorded_backing_track.errors[:next_part_to_upload][0].should == ValidationMessages::PART_NOT_STARTED + end + + it "no parts" do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.upload_start(1000, "abc") + @recorded_backing_track.upload_next_part(1000, "abc") + @recorded_backing_track.errors.any?.should be_false + @recorded_backing_track.upload_part_complete(1, 1000) + @recorded_backing_track.errors.any?.should be_true + @recorded_backing_track.errors[:next_part_to_upload][0].should == ValidationMessages::PART_NOT_FOUND_IN_AWS + end + + it "enough part failures reset the upload" do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.upload_start(File.size(upload_file), md5) + @recorded_backing_track.upload_next_part(File.size(upload_file), md5) + @recorded_backing_track.errors.any?.should be_false + APP_CONFIG.max_track_part_upload_failures.times do |i| + @recorded_backing_track.upload_part_complete(@recorded_backing_track.next_part_to_upload, File.size(upload_file)) + @recorded_backing_track.errors[:next_part_to_upload] == [ValidationMessages::PART_NOT_FOUND_IN_AWS] + part_failure_rollover = i == APP_CONFIG.max_track_part_upload_failures - 1 + expected_is_part_uploading = !part_failure_rollover + expected_part_failures = part_failure_rollover ? 0 : i + 1 + @recorded_backing_track.reload + @recorded_backing_track.is_part_uploading.should == expected_is_part_uploading + @recorded_backing_track.part_failures.should == expected_part_failures + end + + @recorded_backing_track.reload + @recorded_backing_track.upload_failures.should == 1 + @recorded_backing_track.file_offset.should == 0 + @recorded_backing_track.next_part_to_upload.should == 0 + @recorded_backing_track.upload_id.should be_nil + @recorded_backing_track.md5.should be_nil + @recorded_backing_track.length.should == 0 + end + + it "enough upload failures fails the upload forever" do + APP_CONFIG.stub(:max_track_upload_failures).and_return(1) + APP_CONFIG.stub(:max_track_part_upload_failures).and_return(2) + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + APP_CONFIG.max_track_upload_failures.times do |j| + @recorded_backing_track.upload_start(File.size(upload_file), md5) + @recorded_backing_track.upload_next_part(File.size(upload_file), md5) + @recorded_backing_track.errors.any?.should be_false + APP_CONFIG.max_track_part_upload_failures.times do |i| + @recorded_backing_track.upload_part_complete(@recorded_backing_track.next_part_to_upload, File.size(upload_file)) + @recorded_backing_track.errors[:next_part_to_upload] == [ValidationMessages::PART_NOT_FOUND_IN_AWS] + part_failure_rollover = i == APP_CONFIG.max_track_part_upload_failures - 1 + expected_is_part_uploading = part_failure_rollover ? false : true + expected_part_failures = part_failure_rollover ? 0 : i + 1 + @recorded_backing_track.reload + @recorded_backing_track.is_part_uploading.should == expected_is_part_uploading + @recorded_backing_track.part_failures.should == expected_part_failures + end + @recorded_backing_track.upload_failures.should == j + 1 + end + + @recorded_backing_track.reload + @recorded_backing_track.upload_failures.should == APP_CONFIG.max_track_upload_failures + @recorded_backing_track.file_offset.should == 0 + @recorded_backing_track.next_part_to_upload.should == 0 + @recorded_backing_track.upload_id.should be_nil + @recorded_backing_track.md5.should be_nil + @recorded_backing_track.length.should == 0 + + # try to poke it and get the right kind of error back + @recorded_backing_track.upload_next_part(File.size(upload_file), md5) + @recorded_backing_track.errors[:upload_failures] = [ValidationMessages::UPLOAD_FAILURES_EXCEEDED] + end + + describe "correctly uploaded a file" do + + before do + @recorded_backing_track = RecordedBackingTrack.create_from_backing_track(@backing_track, @recording) + @recorded_backing_track.upload_start(File.size(upload_file), md5) + @recorded_backing_track.upload_next_part(File.size(upload_file), md5) + signed_data = @recorded_backing_track.upload_sign(md5) + @response = put_file_to_aws(signed_data, upload_file_contents) + @recorded_backing_track.upload_part_complete(@recorded_backing_track.next_part_to_upload, File.size(upload_file)) + @recorded_backing_track.errors.any?.should be_false + @recorded_backing_track.upload_complete + @recorded_backing_track.errors.any?.should be_false + @recorded_backing_track.marking_complete = false + end + + it "can download an updated file" do + @response = RestClient.get @recorded_backing_track.sign_url + @response.body.should == upload_file_contents + end + + it "can't mark completely uploaded twice" do + @recorded_backing_track.upload_complete + @recorded_backing_track.errors.any?.should be_true + @recorded_backing_track.errors[:fully_uploaded][0].should == "already set" + @recorded_backing_track.part_failures.should == 0 + end + + it "can't ask for a next part if fully uploaded" do + @recorded_backing_track.upload_next_part(File.size(upload_file), md5) + @recorded_backing_track.errors.any?.should be_true + @recorded_backing_track.errors[:fully_uploaded][0].should == "already set" + @recorded_backing_track.part_failures.should == 0 + end + + it "can't ask for mark part complete if fully uploaded" do + @recorded_backing_track.upload_part_complete(1, 1000) + @recorded_backing_track.errors.any?.should be_true + @recorded_backing_track.errors[:fully_uploaded][0].should == "already set" + @recorded_backing_track.part_failures.should == 0 + end + end + end +end + diff --git a/ruby/spec/jam_ruby/models/user_sync_spec.rb b/ruby/spec/jam_ruby/models/user_sync_spec.rb index 2bea2d766..b015e77f5 100644 --- a/ruby/spec/jam_ruby/models/user_sync_spec.rb +++ b/ruby/spec/jam_ruby/models/user_sync_spec.rb @@ -20,6 +20,49 @@ describe UserSync do data[:next].should be_nil end + describe "backing_tracks" do + + let!(:recording1) { + recording = FactoryGirl.create(:recording, owner: user1, band: nil, duration:1) + recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: recording.owner, fully_uploaded:false) + recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: user2, fully_uploaded:false) + recording.recorded_backing_tracks << FactoryGirl.create(:recorded_backing_track, recording: recording, user: recording.owner, fully_uploaded:false) + recording.save! + recording.reload + recording + } + + let(:sorted_tracks) { + Array.new(recording1.recorded_tracks).sort! {|a, b| + if a.created_at == b.created_at + a.id <=> b.id + else + a.created_at <=> b.created_at + end + } + } + + # backing tracks should only list download, or upload, for the person who opened it, for legal reasons + it "lists backing track for opener" do + data = UserSync.index({user_id: user1.id}) + data[:next].should be_nil + user_syncs = data[:query] + user_syncs.count.should eq(3) + user_syncs[0].recorded_track.should == sorted_tracks[0] + user_syncs[1].recorded_track.should == sorted_tracks[1] + user_syncs[2].recorded_backing_track.should == recording1.recorded_backing_tracks[0] + end + + it "does not list backing track for non-opener" do + data = UserSync.index({user_id: user2.id}) + data[:next].should be_nil + user_syncs = data[:query] + user_syncs.count.should eq(2) + user_syncs[0].recorded_track.should == sorted_tracks[0] + user_syncs[1].recorded_track.should == sorted_tracks[1] + end + end + it "one mix and quick mix" do mix = FactoryGirl.create(:mix) mix.recording.duration = 1 diff --git a/ruby/spec/spec_helper.rb b/ruby/spec/spec_helper.rb index 890a195c0..16ac84514 100644 --- a/ruby/spec/spec_helper.rb +++ b/ruby/spec/spec_helper.rb @@ -46,6 +46,7 @@ ActiveRecord::Base.add_observer InvitedUserObserver.instance ActiveRecord::Base.add_observer UserObserver.instance ActiveRecord::Base.add_observer FeedbackObserver.instance ActiveRecord::Base.add_observer RecordedTrackObserver.instance +ActiveRecord::Base.add_observer RecordedBackingTrackObserver.instance ActiveRecord::Base.add_observer QuickMixObserver.instance #RecordedTrack.observers.disable :all # only a few tests want this observer active diff --git a/web/app/assets/javascripts/dialog/localRecordingsDialog.js b/web/app/assets/javascripts/dialog/localRecordingsDialog.js index 27e1bc3e8..d08911d7c 100644 --- a/web/app/assets/javascripts/dialog/localRecordingsDialog.js +++ b/web/app/assets/javascripts/dialog/localRecordingsDialog.js @@ -112,6 +112,10 @@ // tell the server we are about to start a recording rest.startPlayClaimedRecording({id: context.JK.CurrentSessionModel.id(), claimed_recording_id: claimedRecording.id}) .done(function(response) { + + // update session info + context.JK.CurrentSessionModel.updateSession(response); + var recordingId = $(this).attr('data-recording-id'); var openRecordingResult = context.jamClient.OpenRecording(claimedRecording.recording); diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 85e400115..feb1f2e28 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -890,66 +890,94 @@ logger.warn("some tracks are open that we don't know how to show") } - } - + // this method is pretty complicated because it forks on a key bit of state: + // sessionModel.isPlayingRecording() + // a backing track opened as part of a recording has a different behavior and presence on the server (recording.recorded_backing_tracks) + // than a backing track opend ad-hoc (connection.backing_tracks) function renderBackingTracks(backingTrackMixers) { - var backingTrack = sessionModel.backingTrack() - var backingTrackPath = backingTrack ? backingTrack.path : null - var name = backingTrackPath - console.log("Opening backing track ", backingTrackPath, backingTrack) - // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) - // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener - var isOpener = backingTrackMixers[0].group_id == ChannelGroupIds.MediaTrackGroup; + var backingTracks = [] + if(sessionModel.isPlayingRecording()) { + // only return managed mixers for recorded backing tracks + backingTrackMixers = context._.filter(backingTrackMixers, function(mixer){return mixer.managed}) + backingTracks = sessionModel.recordedBackingTracks(); + } + else { + // only return un-managed (ad-hoc) mixers for normal backing tracks + backingTracks = sessionModel.backingTracks(); + backingTrackMixers = context._.filter(backingTrackMixers, function(mixer){return !mixer.managed}) + if(backingTrackMixers.length > 1) { + app.notify({ + title: "Multiple Backing Tracks Encounterd", + text: "Only one backing track can be open a time.", + icon_url: "/assets/content/icon_alert_big.png" + }); + return false; + } + } - - // using the server's info in conjuction with the client's, draw the recording tracks - if(backingTrackPath && backingTrackMixers.length > 0) { - var backingTrack = {path: backingTrackPath} - //backingTrackPath sessionModel.getCurrentSession().backing_track_path - - $('.session-recording-name').text(name); - var noCorrespondingTracks = false; - var mixer = backingTrackMixers[0] - var preMasteredClass = ""; + var noCorrespondingTracks = false; + $.each(backingTrackMixers, function(index, mixer) { + + console.log("hunting for backing tracks:", backingTrackMixers, backingTracks) // find the track or tracks that correspond to the mixer var correspondingTracks = [] - correspondingTracks.push(backingTrack); - - if(correspondingTracks.length == 0) { + + var noCorrespondingTracks = false; + if(sessionModel.isPlayingRecording()) { + $.each(backingTracks, function (i, backingTrack) { + if(mixer.persisted_track_id == backingTrack.client_track_id) { + correspondingTracks.push(backingTrack) + } + }); + } + else + { + // if this is just an open backing track, then we can assume that the 1st backingTrackMixer is ours + correspondingTracks.push(backingTracks[0]) + } + + if (correspondingTracks.length == 0) { noCorrespondingTracks = true; app.notify({ - title: "Unable to Open BackingTrack", + title: "Unable to Open Backing Track", text: "Could not correlate server and client tracks", - icon_url: "/assets/content/icon_alert_big.png"}); + icon_url: "/assets/content/icon_alert_big.png" + }); return false; } - // prune found recorded tracks - // backingTracks = $.grep(backingTracks, function(value) { - // return $.inArray(value, correspondingTracks) < 0; - // }); + // now we have backing track and mixer in hand; we can render + var backingTrack = correspondingTracks[0] - var oneOfTheTracks = correspondingTracks[0]; - var instrumentIcon = context.JK.getInstrumentIcon45(oneOfTheTracks.instrument_id); + // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between JamTrack vs Peer) + // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener + var isOpener = mixer.group_id == ChannelGroupIds.MediaTrackGroup; + + if(!sessionModel.isPlayingRecording()) { + // if a recording is being played back, do not set this header, because renderRecordedTracks already did + // ugly. + $('.session-recording-name').text(backingTrack.filename); + } + + var instrumentIcon = context.JK.getInstrumentIcon45(backingTrack.instrument_id); var photoUrl = "/assets/content/icon_recording.png"; - // Default trackData to participant + no Mixer state. var trackData = { - trackId: oneOfTheTracks.id, - clientId: oneOfTheTracks.client_id, - name: name, + trackId: backingTrack.id, + clientId: backingTrack.client_id, + name: backingTrack.filename, instrumentIcon: instrumentIcon, avatar: photoUrl, latency: "good", gainPercent: 0, muteClass: 'muted', - showLoop: true, + showLoop: !sessionModel.isPlayingRecording(), mixerId: "", - avatarClass : 'avatar-recording', + avatarClass: 'avatar-recording', preMasteredClass: "" }; @@ -965,12 +993,12 @@ trackData.vuMixerId = mixer.id; // the master mixer controls the VUs for recordings (no personal controls in either master or personal mode) trackData.muteMixerId = mixer.id; // the master mixer controls the mute for recordings (no personal controls in either master or personal mode) - if(sessionModel.isPersonalMixMode() || !isOpener) { + if (sessionModel.isPersonalMixMode() || !isOpener) { trackData.mediaControlsDisabled = true; trackData.mediaTrackOpener = isOpener; } - _addRecordingTrack(trackData); - }// if + _addRecordingTrack(trackData); + }); } function renderJamTracks(jamTrackMixers) { @@ -2357,8 +2385,10 @@ function closeRecording() { rest.stopPlayClaimedRecording({id: sessionModel.id(), claimed_recording_id: sessionModel.getCurrentSession().claimed_recording.id}) - .done(function() { - sessionModel.refreshCurrentSession(true); + .done(function(response) { + //sessionModel.refreshCurrentSession(true); + // update session info + context.JK.CurrentSessionModel.updateSession(response); }) .fail(function(jqXHR) { app.notify({ diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 7f2ce0776..f2143684d 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -69,7 +69,7 @@ function isPlayingRecording() { // this is the server's state; there is no guarantee that the local tracks // requested from the backend will have corresponding track information - return currentSession && currentSession.claimed_recording; + return !!(currentSession && currentSession.claimed_recording); } function recordedTracks() { @@ -81,6 +81,28 @@ } } + function recordedBackingTracks() { + if(currentSession && currentSession.claimed_recording) { + return currentSession.claimed_recording.recording.recorded_backing_tracks + } + else { + return null; + } + } + + function backingTracks() { + var backingTracks = [] + // this may be wrong if we loosen the idea that only one person can have a backing track open. + // but for now, the 1st person we find with a backing track open is all there is to find... + context._.each(participants(), function(participant) { + if(participant.backing_tracks.length > 0) { + backingTracks = participant.backing_tracks; + return false; // break + } + }) + return backingTracks; + } + function jamTracks() { if(currentSession && currentSession.jam_track) { return currentSession.jam_track.tracks @@ -346,6 +368,25 @@ } } + function updateSession(response) { + updateSessionInfo(response, null, true); + } + + function updateSessionInfo(response, callback, force) { + if(force === true || currentTrackChanges < response.track_changes_counter) { + logger.debug("updating current track changes from %o to %o", currentTrackChanges, response.track_changes_counter) + currentTrackChanges = response.track_changes_counter; + sendClientParticipantChanges(currentSession, response); + updateCurrentSession(response); + if(callback != null) { + callback(); + } + } + else { + logger.info("ignoring refresh because we already have current: " + currentTrackChanges + ", seen: " + response.track_changes_counter); + } + } + /** * Reload the session data from the REST server, calling * the provided callback when complete. @@ -369,18 +410,7 @@ type: "GET", url: url, success: function(response) { - if(force === true || currentTrackChanges < response.track_changes_counter) { - logger.debug("updating current track changes from %o to %o", currentTrackChanges, response.track_changes_counter) - currentTrackChanges = response.track_changes_counter; - sendClientParticipantChanges(currentSession, response); - updateCurrentSession(response); - if(callback != null) { - callback(); - } - } - else { - logger.info("ignoring refresh because we already have current: " + currentTrackChanges + ", seen: " + response.track_changes_counter); - } + updateSessionInfo(response, callback, force); }, error: function(jqXHR) { if(jqXHR.status != 404) { @@ -572,12 +602,10 @@ var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); // backingTracks can be passed in as an optimization, so that we don't hit the backend excessively - if(backingTracks === undefined) { - backingTracks = context.JK.TrackHelpers.getBackingTracks(context.jamClient); + if(backingTracks === undefined ) { + backingTracks = context.JK.TrackHelpers.getBackingTracks(context.jamClient); } - console.log('backingTracks', backingTracks); - // create a trackSync request based on backend data var syncTrackRequest = {}; syncTrackRequest.client_id = app.clientId; @@ -777,6 +805,8 @@ this.id = id; this.start = start; this.backingTrack = backingTrack; + this.backingTracks = backingTracks; + this.recordedBackingTracks = recordedBackingTracks; this.metronomeActive = metronomeActive; this.setUserTracks = setUserTracks; this.recordedTracks = recordedTracks; @@ -785,6 +815,7 @@ this.joinSession = joinSession; this.leaveCurrentSession = leaveCurrentSession; this.refreshCurrentSession = refreshCurrentSession; + this.updateSession = updateSession; this.subscribe = subscribe; this.participantForClientId = participantForClientId; this.isPlayingRecording = isPlayingRecording; diff --git a/web/app/assets/javascripts/sync_viewer.js.coffee b/web/app/assets/javascripts/sync_viewer.js.coffee index 3215359e2..3ba40ffb3 100644 --- a/web/app/assets/javascripts/sync_viewer.js.coffee +++ b/web/app/assets/javascripts/sync_viewer.js.coffee @@ -27,16 +27,19 @@ context.JK.SyncViewer = class SyncViewer @list = @root.find('.list') @logList = @root.find('.log-list') @templateRecordedTrack = $('#template-sync-viewer-recorded-track') + @templateRecordedBackingTrack = $('#template-sync-viewer-recorded-backing-track') @templateStreamMix = $('#template-sync-viewer-stream-mix') @templateMix = $('#template-sync-viewer-mix') @templateNoSyncs = $('#template-sync-viewer-no-syncs') @templateRecordingWrapperDetails = $('#template-sync-viewer-recording-wrapper-details') @templateHoverRecordedTrack = $('#template-sync-viewer-hover-recorded-track') + @templateHoverRecordedBackingTrack = $('#template-sync-viewer-hover-recorded-backing-track') @templateHoverMix = $('#template-sync-viewer-hover-mix') @templateDownloadReset = $('#template-sync-viewer-download-progress-reset') @templateUploadReset = $('#template-sync-viewer-upload-progress-reset') @templateGenericCommand = $('#template-sync-viewer-generic-command') @templateRecordedTrackCommand = $('#template-sync-viewer-recorded-track-command') + @templateRecordedBackingTrackCommand = $('#template-sync-viewer-recorded-backing-track-command') @templateLogItem = $('#template-sync-viewer-log-item') @tabSelectors = @root.find('.dialog-tabs .tab') @tabs = @root.find('.tab-content') @@ -50,7 +53,8 @@ context.JK.SyncViewer = class SyncViewer them_upload_soon: 'them-upload-soon' missing: 'missing', me_uploaded: 'me-uploaded', - them_uploaded: 'them-uploaded' + them_uploaded: 'them-uploaded', + not_mine: 'not-mine' } @clientStates = { unknown: 'unknown', @@ -58,7 +62,8 @@ context.JK.SyncViewer = class SyncViewer hq: 'hq', sq: 'sq', missing: 'missing', - discarded: 'discarded' + discarded: 'discarded', + not_mine: 'not-mine' } throw "no sync-viewer" if not @root.exists() @@ -329,12 +334,138 @@ context.JK.SyncViewer = class SyncViewer $clientRetry.hide() $uploadRetry.hide() + updateBackingTrackState: ($track) => + clientInfo = $track.data('client-info') + serverInfo = $track.data('server-info') + myTrack = serverInfo.user.id == context.JK.currentUserId + + # determine client state + clientStateMsg = 'UNKNOWN' + clientStateClass = 'unknown' + clientState = @clientStates.unknown + + if serverInfo.mine + if serverInfo.download.should_download + if serverInfo.download.too_many_downloads + clientStateMsg = 'EXCESS DOWNLOADS' + clientStateClass = 'error' + clientState = @clientStates.too_many_uploads + else + if clientInfo? + if clientInfo.local_state == 'HQ' + clientStateMsg = 'HIGHEST QUALITY' + clientStateClass = 'hq' + clientState = @clientStates.hq + else if clientInfo.local_state == 'MISSING' + clientStateMsg = 'MISSING' + clientStateClass = 'missing' + clientState = @clientStates.missing + else + clientStateMsg = 'MISSING' + clientStateClass = 'missing' + clientState = @clientStates.missing + else + clientStateMsg = 'DISCARDED' + clientStateClass = 'discarded' + clientState = @clientStates.discarded + else + clientStateMsg = 'NOT MINE' + clientStateClass = 'not_mine' + clientState = @clientStates.not_mine + + # determine upload state + uploadStateMsg = 'UNKNOWN' + uploadStateClass = 'unknown' + uploadState = @uploadStates.unknown + + if serverInfo.mine + if !serverInfo.fully_uploaded + if serverInfo.upload.too_many_upload_failures + uploadStateMsg = 'UPLOAD FAILURE' + uploadStateClass = 'error' + uploadState = @uploadStates.too_many_upload_failures + else + if myTrack + if clientInfo? + if clientInfo.local_state == 'HQ' + uploadStateMsg = 'PENDING UPLOAD' + uploadStateClass = 'upload-soon' + uploadState = @uploadStates.me_upload_soon + else + uploadStateMsg = 'MISSING' + uploadStateClass = 'missing' + uploadState = @uploadStates.missing + else + uploadStateMsg = 'MISSING' + uploadStateClass = 'missing' + uploadState = @uploadStates.missing + else + uploadStateMsg = 'PENDING UPLOAD' + uploadStateClass = 'upload-soon' + uploadState = @uploadStates.them_upload_soon + else + uploadStateMsg = 'UPLOADED' + uploadStateClass = 'uploaded' + if myTrack + uploadState = @uploadStates.me_uploaded + else + uploadState = @uploadStates.them_uploaded + else + uploadStateMsg = 'NOT MINE' + uploadStateClass = 'not_mine' + uploadState = @uploadStates.not_mine + + + $clientState = $track.find('.client-state') + $clientStateMsg = $clientState.find('.msg') + $clientStateProgress = $clientState.find('.progress') + $uploadState = $track.find('.upload-state') + $uploadStateMsg = $uploadState.find('.msg') + $uploadStateProgress = $uploadState.find('.progress') + + $clientState.removeClass('discarded missing hq unknown error not-mine').addClass(clientStateClass).attr('data-state', clientState).data('custom-class', clientStateClass) + $clientStateMsg.text(clientStateMsg) + $clientStateProgress.css('width', '0') + $uploadState.removeClass('upload-soon error unknown missing uploaded not-mine').addClass(uploadStateClass).attr('data-state', uploadState).data('custom-class', uploadStateClass) + $uploadStateMsg.text(uploadStateMsg) + $uploadStateProgress.css('width', '0') + + # this allows us to make styling decisions based on the combination of both client and upload state. + $track.addClass("clientState-#{clientStateClass}").addClass("uploadState-#{uploadStateClass}") + + $clientRetry = $clientState.find('.retry') + $uploadRetry = $uploadState.find('.retry') + + if gon.isNativeClient + # handle client state + + # only show RETRY button if you have a SQ or if it's missing, and it's been uploaded already + if (clientState == @clientStates.missing) and (uploadState == @uploadStates.me_uploaded or uploadState == @uploadStates.them_uploaded) + $clientRetry.show() + else + $clientRetry.hide() + + # only show RETRY button if you have the HQ track, it's your track, and the server doesn't yet have it + if myTrack and @clientStates.hq and (uploadState == @uploadStates.error or uploadState == @uploadStates.me_upload_soon) + $uploadRetry.show() + else + $uploadRetry.hide() + else + $clientRetry.hide() + $uploadRetry.hide() + + associateClientInfo: (recording) => for clientInfo in recording.local_tracks $track = @list.find(".recorded-track[data-recording-id='#{recording.recording_id}'][data-client-track-id='#{clientInfo.client_track_id}']") $track.data('client-info', clientInfo) $track.data('total-size', recording.size) + for clientInfo in recording.backing_tracks + $track = @list.find(".recorded-backing-track[data-recording-id='#{recording.recording_id}'][data-client-track-id='#{clientInfo.client_track_id}']") + $track.data('client-info', clientInfo) + $track.data('total-size', recording.size) + $track = @list.find(".mix[data-recording-id='#{recording.recording_id}']") $track.data('client-info', recording.mix) $track.data('total-size', recording.size) @@ -457,11 +588,77 @@ context.JK.SyncViewer = class SyncViewer uploadStateClass: uploadStateClass} {variable: 'data'}) - onHoverOfStateIndicator: () -> + displayBackingTrackHover: ($recordedTrack) => + $clientState = $recordedTrack.find('.client-state') + $clientStateMsg = $clientState.find('.msg') + clientStateClass = $clientState.data('custom-class') + clientState = $clientState.attr('data-state') + clientInfo = $recordedTrack.data('client-info') + + $uploadState = $recordedTrack.find('.upload-state') + $uploadStateMsg = $uploadState.find('.msg') + uploadStateClass = $uploadState.data('custom-class') + uploadState = $uploadState.attr('data-state') + serverInfo = $recordedTrack.data('server-info') + + # decide on special case strings first + + summary = '' + if clientState == @clientStates.not_mine && @uploadStates.them_uploaded + # this is not our backing track + summary = "#{serverInfo.user.name} opened this backing track. Due to legal concerns, we can not distribute it to you." + else if clientState == @clientStates.not_mine && @uploadStates.them_upload_soon + # this is not our backing track + summary = "#{serverInfo.user.name} has not yet uploaded their backing track." + else if clientState == @clientStates.missing && uploadState == @uploadStates.me_uploaded + # we have no version of the track at all, and the other user has uploaded the HQ version... it's coming soon! + summary = "You have previously uploaded the high-quality version of this track. JamKazam will soon restore it and then this backing track will no longer be missing." + else if clientState == @clientStates.discarded && (uploadState == @uploadStates.me_uploaded or uploadState == @uploadStates.them_uploaded) + # we decided not to keep the recording... so it's important to clarify why they are seeing it at all + summary = "When this recording was made, you elected to not keep it. JamKazam already uploaded your high-quality backing track for the recording, because at least one other person decided to keep the recording and needs your backing track to make a high-quality mix." + else if clientState == @clientStates.discarded + # we decided not to keep the recording... so it's important to clarify why they are seeing it at all + summary = "When this recording was made, you elected to not keep it. JamKazam will still try to upload your high-quality backing track for the recording, because at least one other person decided to keep the recording and needs your backing track to make a high-quality mix." + else if clientState == @clientStates.hq and ( uploadState == @uploadStates.me_uploaded ) + summary = "Both you and the JamKazam server have the high-quality version of this track. Once all the other tracks for this recording are also synchronized, then the final mix can be made." + + clientStateDefinition = switch clientState + when @clientStates.too_many_downloads then "This backing track has been downloaded an unusually large number of times. No more downloads are allowed." + when @clientStates.hq then "HIGHEST QUALITY means you have the original version of this backing track." + when @clientStates.missing then "MISSING means you do not have this backing track anymore." + when @clientStates.discarded then "DISCARDED means you chose to not keep this recording when the recording was over." + when @clientStates.not_mine then "NOT MINE means someone else opened and played this backing track." + else 'There is no help for this state' + + uploadStateDefinition = switch uploadState + when @uploadStates.too_many_upload_failures then "Failed attempts at uploading this backing track has happened an unusually large times. No more uploads will be attempted." + when @uploadStates.me_upload_soon then "PENDING UPLOAD means your JamKazam application will upload this track soon." + when @uploadStates.them_up_soon then "PENDING UPLOAD means #{serverInfo.user.name} will upload this track soon." + when @uploadStates.me_uploaded then "UPLOADED means you have already uploaded this track." + when @uploadStates.them_uploaded then "UPLOADED means #{serverInfo.user.name} has already uploaded this track." + when @uploadStates.missing then "MISSING means your JamKazam application does not have this track, and the server does not either." + when @uploadStates.not_mine then "NOT MINE means someone else opened and played this backing track." + + context._.template(@templateHoverRecordedBackingTrack.html(), + {summary: summary, + clientStateDefinition: clientStateDefinition, + uploadStateDefinition: uploadStateDefinition, + clientStateMsg: $clientStateMsg.text(), + uploadStateMsg: $uploadStateMsg.text(), + clientStateClass: clientStateClass, + uploadStateClass: uploadStateClass} + {variable: 'data'}) + + onTrackHoverOfStateIndicator: () -> $recordedTrack = $(this).closest('.recorded-track.sync') self = $recordedTrack.data('sync-viewer') self.displayTrackHover($recordedTrack) + onBackingTrackHoverOfStateIndicator: () -> + $recordedTrack = $(this).closest('.recorded-backing-track.sync') + self = $recordedTrack.data('sync-viewer') + self.displayBackingTrackHover($recordedTrack) + onStreamMixHover: () -> $streamMix = $(this).closest('.stream-mix.sync') self = $streamMix.data('sync-viewer') @@ -548,8 +745,8 @@ context.JK.SyncViewer = class SyncViewer $uploadStateRetry.click(this.retryUploadRecordedTrack) context.JK.bindHoverEvents($track) context.JK.bindInstrumentHover($track, {positions:['top'], shrinkToFit: true}); - context.JK.hoverBubble($clientState, this.onHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['left']}) - context.JK.hoverBubble($uploadState, this.onHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['right']}) + context.JK.hoverBubble($clientState, this.onTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['left']}) + context.JK.hoverBubble($uploadState, this.onTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['right']}) $clientState.addClass('is-native-client') if gon.isNativeClient $uploadState.addClass('is-native-client') if gon.isNativeClient $track diff --git a/web/app/assets/javascripts/trackHelpers.js b/web/app/assets/javascripts/trackHelpers.js index f75ba85a3..a08885af3 100644 --- a/web/app/assets/javascripts/trackHelpers.js +++ b/web/app/assets/javascripts/trackHelpers.js @@ -33,14 +33,20 @@ getBackingTracks: function(jamClient) { var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 4); + console.log("mediaTracks", mediaTracks) var backingTracks = [] context._.each(mediaTracks, function(mediaTrack) { - var track = {}; - track.client_track_id = mediaTrack.id; - track.client_resource_id = mediaTrack.rid; - track.filename = mediaTrack.id; - backingTracks.push(track); + // the check for 'not managed' means this is not a track opened by a recording, basically + // we do not try and sync these sorts of backing tracks to the server, because they + // are already encompassed by + if(mediaTrack.media_type == "BackingTrack" && !mediaTrack.managed) { + var track = {}; + track.client_track_id = mediaTrack.persisted_track_id; + track.client_resource_id = mediaTrack.rid; + track.filename = mediaTrack.filename; + backingTracks.push(track); + } }) return backingTracks; diff --git a/web/app/assets/stylesheets/client/help.css.scss b/web/app/assets/stylesheets/client/help.css.scss index 4755d711f..512988936 100644 --- a/web/app/assets/stylesheets/client/help.css.scss +++ b/web/app/assets/stylesheets/client/help.css.scss @@ -45,7 +45,7 @@ body.jam, body.web, .dialog{ } } - .help-hover-recorded-tracks, .help-hover-stream-mix { + .help-hover-recorded-tracks, .help-hover-stream-mix, .help-hover-recorded-backing-tracks { font-size:12px; padding:5px; diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 6244e1e17..842a9d361 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -3,6 +3,7 @@ class ApiRecordingsController < ApiController before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim ] before_filter :lookup_recorded_track, :only => [ :download, :upload_next_part, :upload_sign, :upload_part_complete, :upload_complete ] + before_filter :lookup_recorded_backing_track, :only => [ :backing_track_download, :backing_track_upload_next_part, :backing_track_upload_sign, :backing_track_upload_part_complete, :backing_track_upload_complete ] before_filter :lookup_recorded_video, :only => [ :video_upload_sign, :video_upload_start, :video_upload_complete ] before_filter :lookup_stream_mix, :only => [ :upload_next_part_stream_mix, :upload_sign_stream_mix, :upload_part_complete_stream_mix, :upload_complete_stream_mix ] @@ -43,7 +44,7 @@ class ApiRecordingsController < ApiController @recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id(params[:id], params[:track_id]) end - def download + def download # track raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.can_download?(current_user) @recorded_track.current_user = current_user @@ -58,6 +59,21 @@ class ApiRecordingsController < ApiController end end + def backing_track_download + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_backing_track.can_download?(current_user) + + @recorded_backing_track.current_user = current_user + @recorded_backing_track.update_download_count + + @recorded_backing_track.valid? + if !@recorded_backing_track.errors.any? + @recorded_backing_track.save! + redirect_to @recorded_backing_track.sign_url + else + render :json => { :message => "download limit surpassed" }, :status => 404 + end + end + def start music_session = ActiveMusicSession.find(params[:music_session_id]) @@ -227,6 +243,61 @@ class ApiRecordingsController < ApiController end end + def backing_track_upload_next_part + length = params[:length] + md5 = params[:md5] + + @recorded_backing_track.upload_next_part(length, md5) + + if @recorded_backing_track.errors.any? + + response.status = :unprocessable_entity + # this is not typical, but please don't change this line unless you are sure it won't break anything + # this is needed because after_rollback in the RecordedTrackObserver touches the model and something about it's + # state doesn't cause errors to shoot out like normal. + render :json => { :errors => @recorded_backing_track.errors }, :status => 422 + else + result = { + :part => @recorded_backing_track.next_part_to_upload, + :offset => @recorded_backing_track.file_offset.to_s + } + + render :json => result, :status => 200 + end + + end + + def backing_track_upload_sign + render :json => @recorded_backing_track.upload_sign(params[:md5]), :status => 200 + end + + def backing_track_upload_part_complete + part = params[:part] + offset = params[:offset] + + @recorded_backing_track.upload_part_complete(part, offset) + + if @recorded_backing_track.errors.any? + response.status = :unprocessable_entity + respond_with @recorded_backing_track + else + render :json => {}, :status => 200 + end + end + + def backing_track_upload_complete + @recorded_backing_track.upload_complete + @recorded_backing_track.recording.upload_complete + + if @recorded_backing_track.errors.any? + response.status = :unprocessable_entity + respond_with @recorded_backing_track + return + else + render :json => {}, :status => 200 + end + end + # POST /api/recordings/:id/videos/:video_id/upload_sign def video_upload_sign length = params[:length] @@ -314,6 +385,11 @@ class ApiRecordingsController < ApiController raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.recording.has_access?(current_user) end + def lookup_recorded_backing_track + @recorded_backing_track = RecordedBackingTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id]) + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_backing_track.recording.has_access?(current_user) + end + def lookup_stream_mix @quick_mix = QuickMix.find_by_recording_id_and_user_id!(params[:id], current_user.id) raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @quick_mix.recording.has_access?(current_user) diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index dbc1c7fd9..b038a0102 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -2,6 +2,8 @@ # I don't think I need to include URLs since that's handled by syncing. This is just to make the metadata # depictable. +# THIS IS USED DIRECTLY BY THE CLIENT. DO NOT CHANGE FORMAT UNLESS YOU VERIFY CLIENT FIRST. IN PARTICULAR RecordingFileStorage#getLocalRecordingState + object @claimed_recording attributes :id, :name, :description, :is_public, :genre_id, :discarded @@ -36,6 +38,18 @@ child(:recording => :recording) { } } + child(:recorded_backing_tracks => :recorded_backing_tracks) { + attributes :id, :fully_uploaded, :client_track_id, :client_id, :filename + + child(:user => :user) { + attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :photo_url + } + + node :mine do |user| + user == current_user + end + } + child(:comments => :comments) { attributes :comment, :created_at diff --git a/web/app/views/api_music_sessions/show.rabl b/web/app/views/api_music_sessions/show.rabl index 1d87b55c1..cd277c3fe 100644 --- a/web/app/views/api_music_sessions/show.rabl +++ b/web/app/views/api_music_sessions/show.rabl @@ -54,6 +54,10 @@ else child(:tracks => :tracks) { attributes :id, :connection_id, :instrument_id, :sound, :client_track_id, :client_resource_id, :updated_at } + + child(:backing_tracks => :backing_tracks) { + attributes :id, :connection_id, :filename, :client_track_id, :client_resource_id, :updated_at + } } child({:invitations => :invitations}) { @@ -114,6 +118,14 @@ else attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url } } + + child(:recorded_backing_tracks => :recorded_backing_tracks) { + attributes :id, :fully_uploaded, :client_track_id, :client_id, :filename + + child(:user => :user) { + attributes :id, :first_name, :last_name, :city, :state, :country, :photo_url + } + } } } diff --git a/web/app/views/api_recordings/show.rabl b/web/app/views/api_recordings/show.rabl index 1fe574026..f3c817b07 100644 --- a/web/app/views/api_recordings/show.rabl +++ b/web/app/views/api_recordings/show.rabl @@ -27,6 +27,12 @@ child(:recorded_tracks => :recorded_tracks) { end } +child(:recorded_backing_tracks => :recorded_backing_tracks) { + node do |recorded_backing_track| + partial("api_recordings/show_recorded_backing_track", :object => recorded_backing_track) + end +} + child(:comments => :comments) { attributes :comment, :created_at diff --git a/web/app/views/api_recordings/show_recorded_backing_track.rabl b/web/app/views/api_recordings/show_recorded_backing_track.rabl new file mode 100644 index 000000000..1c9e1a078 --- /dev/null +++ b/web/app/views/api_recordings/show_recorded_backing_track.rabl @@ -0,0 +1,7 @@ +object @recorded_backing_track + +attributes :id, :fully_uploaded, :client_track_id, :client_id, :recording_id, :filename + +child(:user => :user) { + attributes :id, :first_name, :last_name, :city, :state, :country, :location, :photo_url +} \ No newline at end of file diff --git a/web/app/views/api_user_syncs/show.rabl b/web/app/views/api_user_syncs/show.rabl index 0c63ba010..730120276 100644 --- a/web/app/views/api_user_syncs/show.rabl +++ b/web/app/views/api_user_syncs/show.rabl @@ -18,7 +18,6 @@ glue :recorded_track do partial("api_recordings/show", :object => recorded_track.recording) end - node :upload do |recorded_track| { should_upload: true, @@ -35,6 +34,45 @@ glue :recorded_track do end + +glue :recorded_backing_track do + + @object.current_user = current_user + + node :type do |i| + 'recorded_backing_track' + end + + attributes :id, :recording_id, :client_id, :track_id, :client_track_id, :md5, :length, :download_count, :fully_uploaded, :upload_failures, :part_failures, :created_at, :filename + + node :user do |recorded_backing_track| + partial("api_users/show_minimal", :object => recorded_backing_track.user) + end + + node :recording do |recorded_backing_track| + partial("api_recordings/show", :object => recorded_backing_track.recording) + end + + node :mine do |recorded_backing_track| + recorded_backing_track.user == current_user + end + + node :upload do |recorded_backing_track| + { + should_upload: true, + too_many_upload_failures: recorded_backing_track.too_many_upload_failures? + } + end + + node :download do |recorded_backing_track| + { + should_download: recorded_backing_track.can_download?(current_user), + too_many_downloads: recorded_backing_track.too_many_downloads? + } + end + +end + glue :mix do @object.current_user = current_user diff --git a/web/app/views/clients/_sync_viewer_templates.html.slim b/web/app/views/clients/_sync_viewer_templates.html.slim index 4c08f9ab9..92f0953bd 100644 --- a/web/app/views/clients/_sync_viewer_templates.html.slim +++ b/web/app/views/clients/_sync_viewer_templates.html.slim @@ -59,6 +59,23 @@ script type="text/template" id='template-sync-viewer-recorded-track' a.retry href='#' = image_tag('content/icon_resync.png', width:12, height: 14) +script type="text/template" id='template-sync-viewer-recorded-backing-track' + .recorded-track.sync data-id="{{data.id}}" data-recording-id="{{data.recording_id}}" data-client-id="{{data.client_id}}" data-client-track-id="{{data.client_track_id}}" data-track-id="{{data.backing_track_id}}" data-fully-uploaded="{{data.fully_uploaded}}" + .type + span.text BACKING + a.avatar-tiny href="#" user-id="{{data.user.id}}" hoveraction="musician" + img src="{{JK.resolveAvatarUrl(data.user.photo_url)}}" + .client-state.bar + .progress + span.msg + a.retry href='#' + = image_tag('content/icon_resync.png', width:12, height: 14) + .upload-state.bar + .progress + span.msg + a.retry href='#' + = image_tag('content/icon_resync.png', width:12, height: 14) + script type="text/template" id='template-sync-viewer-stream-mix' .stream-mix.sync data-id="{{data.id}}" data-recording-id="{{data.recording_id}}" @@ -128,6 +145,33 @@ script type="text/template" id="template-sync-viewer-hover-recorded-track" | {{data.summary}} | {% } %} +script type="text/template" id="template-sync-viewer-hover-recorded-backing-track" + .help-hover-recorded-backing-tracks + + .client-box + .client-state-info + span.special-text is the file on your system? + .client-state class="{{data.clientStateClass}}" + span.msg + | {{data.clientStateMsg}} + .client-state-definition.sync-definition + | {{data.clientStateDefinition}} + .upload-box + .upload-state-info + span.special-text is it uploaded? + .upload-state class="{{data.uploadStateClass}}" + span.msg + | {{data.uploadStateMsg}} + .upload-state-definition.sync-definition + | {{data.uploadStateDefinition}} + br clear="both" + + | {% if(data.summary) { %} + .summary + .title what's next? + | {{data.summary}} + | {% } %} + script type="text/template" id="template-sync-viewer-hover-stream-mix" .help-hover-stream-mix @@ -198,6 +242,15 @@ script type="text/template" id="template-sync-viewer-recorded-track-command" img src="{{JK.resolveAvatarUrl(data.user.photo_url)}}" img.instrument-icon data-instrument-id="{{data.instrument_id}}" hoveraction="instrument" src="{{JK.getInstrumentIconMap24()[data.instrument_id].asset}}" +script type="text/template" id="template-sync-viewer-recorded-backing-track-command" + .recorded-backing-track.sync + .type + .progress + span.text + | {{data.action}} BACKING TRACK + a.avatar-tiny href="#" user-id="{{data.user.id}}" hoveraction="musician" + img src="{{JK.resolveAvatarUrl(data.user.photo_url)}}" + script type="text/template" id="template-sync-viewer-log-item" .log class="success-{{data.success}}" .command diff --git a/web/config/application.rb b/web/config/application.rb index 7ffd31ed7..1d76163fd 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -39,7 +39,7 @@ if defined?(Bundler) # Activate observers that should always be running. # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - config.active_record.observers = "JamRuby::InvitedUserObserver", "JamRuby::UserObserver", "JamRuby::FeedbackObserver", "JamRuby::RecordedTrackObserver", "JamRuby::QuickMixObserver" + config.active_record.observers = "JamRuby::InvitedUserObserver", "JamRuby::UserObserver", "JamRuby::FeedbackObserver", "JamRuby::RecordedTrackObserver", "JamRuby::QuickMixObserver", "JamRuby::RecordedBackingTrackObserver" # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. diff --git a/web/config/routes.rb b/web/config/routes.rb index 9cb0ee9da..d50d69a4c 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -456,13 +456,20 @@ SampleApp::Application.routes.draw do match '/recordings/:id/tracks/:track_id/upload_sign' => 'api_recordings#upload_sign', :via => :get match '/recordings/:id/tracks/:track_id/upload_part_complete' => 'api_recordings#upload_part_complete', :via => :post match '/recordings/:id/tracks/:track_id/upload_complete' => 'api_recordings#upload_complete', :via => :post - match '/recordings/:id/stream_mix/upload_next_part' => 'api_recordings#upload_next_part_stream_mix', :via => :get # Recordings - stream_mix match '/recordings/:id/stream_mix/upload_sign' => 'api_recordings#upload_sign_stream_mix', :via => :get match '/recordings/:id/stream_mix/upload_part_complete' => 'api_recordings#upload_part_complete_stream_mix', :via => :post match '/recordings/:id/stream_mix/upload_complete' => 'api_recordings#upload_complete_stream_mix', :via => :post - + match '/recordings/:id/stream_mix/upload_next_part' => 'api_recordings#upload_next_part_stream_mix', :via => :get + + # Recordings - backing tracks + match '/recordings/:id/backing_tracks/:track_id/download' => 'api_recordings#backing_track_download', :via => :get, :as => 'api_recordings_download' + match '/recordings/:id/backing_tracks/:track_id/upload_next_part' => 'api_recordings#backing_track_upload_next_part', :via => :get + match '/recordings/:id/backing_tracks/:track_id/upload_sign' => 'api_recordings#backing_track_upload_sign', :via => :get + match '/recordings/:id/backing_tracks/:track_id/upload_part_complete' => 'api_recordings#backing_track_upload_part_complete', :via => :post + match '/recordings/:id/backing_tracks/:track_id/upload_complete' => 'api_recordings#backing_track_upload_complete', :via => :post + # Recordings - recorded_videos match '/recordings/:id/tracks/:video_id/upload_sign' => 'api_recordings#video_upload_sign', :via => :get match '/recordings/:id/videos/:video_id/upload_start' => 'api_recordings#video_upload_start', :via => :post diff --git a/web/spec/controllers/api_recordings_controller_spec.rb b/web/spec/controllers/api_recordings_controller_spec.rb index e0691cb2d..f09a6130d 100644 --- a/web/spec/controllers/api_recordings_controller_spec.rb +++ b/web/spec/controllers/api_recordings_controller_spec.rb @@ -10,6 +10,7 @@ describe ApiRecordingsController do @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) @connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session) @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) + @backing_track = FactoryGirl.create(:backing_track, :connection => @connection) controller.current_user = @user end @@ -100,7 +101,65 @@ describe ApiRecordingsController do end end - describe "download" do + describe "download track" do + let(:mix) { FactoryGirl.create(:mix) } + + it "should only allow a user to download a track if they have claimed the recording" do + post :start, { :format => 'json', :music_session_id => @music_session.id } + response_body = JSON.parse(response.body) + recording = Recording.find(response_body['id']) + post :stop, { :format => 'json', :id => recording.id } + response.should be_success + end + + + it "is possible" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + controller.current_user = mix.recording.owner + get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + + recorded_track.reload + recorded_track.download_count.should == 1 + + get :download, {id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + + recorded_track.reload + recorded_track.download_count.should == 2 + end + + + it "prevents download after limit is reached" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + recorded_track.download_count = APP_CONFIG.max_audio_downloads + recorded_track.save! + controller.current_user = recorded_track.user + get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 404 + JSON.parse(response.body, symbolize_names: true)[:message].should == "download limit surpassed" + end + + + it "lets admins surpass limit" do + mix.touch + recorded_track = mix.recording.recorded_tracks[0] + recorded_track.download_count = APP_CONFIG.max_audio_downloads + recorded_track.save! + recorded_track.user.admin = true + recorded_track.user.save! + + controller.current_user = recorded_track.user + get :download, {format:'json', id: recorded_track.recording.id, track_id: recorded_track.client_track_id} + response.status.should == 302 + recorded_track.reload + recorded_track.download_count.should == 101 + end + end + + describe "download backing track" do let(:mix) { FactoryGirl.create(:mix) } it "should only allow a user to download a track if they have claimed the recording" do diff --git a/web/spec/factories.rb b/web/spec/factories.rb index bf0e44083..eeb17a8c0 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -274,6 +274,11 @@ FactoryGirl.define do sequence(:client_resource_id) { |n| "resource_id#{n}"} end + factory :backing_track, :class => JamRuby::BackingTrack do + sequence(:client_track_id) { |n| "client_track_id#{n}"} + filename 'foo.mp3' + end + factory :video_source, :class => JamRuby::VideoSource do #client_video_source_id "test_source_id" sequence(:client_video_source_id) { |n| "client_video_source_id#{n}"} @@ -303,6 +308,19 @@ FactoryGirl.define do association :recording, factory: :recording end + factory :recorded_backing_track, :class => JamRuby::RecordedBackingTrack do + sequence(:client_id) { |n| "client_id-#{n}"} + sequence(:backing_track_id) { |n| "track_id-#{n}"} + sequence(:client_track_id) { |n| "client_track_id-#{n}"} + sequence(:filename) { |n| "filename-{#n}"} + sequence(:url) { |n| "/recordings/blah/#{n}"} + md5 'abc' + length 1 + fully_uploaded true + association :user, factory: :user + association :recording, factory: :recording + end + factory :recorded_video, :class => JamRuby::RecordedVideo do sequence(:recording_id) { |n| "recording_id-#{n}"} sequence(:client_video_source_id) { |n| "client_video_source_id-#{n}"} diff --git a/web/spec/spec_helper.rb b/web/spec/spec_helper.rb index 09606e087..fdb947180 100644 --- a/web/spec/spec_helper.rb +++ b/web/spec/spec_helper.rb @@ -192,6 +192,12 @@ bputs "before register capybara" end config.before(:all) do + # to reduce frequency of timeout on initial test + # https://github.com/teampoltergeist/poltergeist/issues/294#issuecomment-72746472 + if self.respond_to? :visit + visit '/assets/application.css' + visit '/assets/application.js' + end end config.before(:each) do From afe9f6570992973e10389601e2124ae024b50399 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 15 Feb 2015 22:14:30 -0600 Subject: [PATCH 42/46] * merged --- ruby/lib/jam_ruby/models/recording.rb | 5 + web/app/assets/javascripts/jam_rest.js | 13 ++ web/app/assets/javascripts/session.js | 120 +++++++++++--- .../assets/javascripts/sync_viewer.js.coffee | 146 ++++++++++++++---- web/app/assets/javascripts/utils.js | 9 ++ .../assets/stylesheets/client/common.css.scss | 8 + .../controllers/api_recordings_controller.rb | 4 + .../views/api_claimed_recordings/show.rabl | 4 +- .../show_recorded_backing_track.rabl | 4 + web/app/views/clients/_session.html.slim | 7 +- .../clients/_sync_viewer_templates.html.slim | 6 +- web/config/application.rb | 1 + web/config/routes.rb | 1 + 13 files changed, 272 insertions(+), 56 deletions(-) diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index 52e4643af..69d690962 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -578,6 +578,11 @@ module JamRuby recorded_tracks.each do |recorded_track| return false unless recorded_track.fully_uploaded end + + recorded_backing_tracks.each do |recorded_backing_track| + return false unless recorded_backing_track.fully_uploaded + end + true end diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index bb1109c9c..76a260b72 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1055,6 +1055,18 @@ }); } + function getRecordedBackingTrack(options) { + var recordingId = options["recording_id"]; + var trackId = options["track_id"]; + + return $.ajax({ + type: "GET", + dataType: "json", + contentType: 'application/json', + url: "/api/recordings/" + recordingId + "/backing_tracks/" + trackId + }); + } + function getRecording(options) { var recordingId = options["id"]; @@ -1624,6 +1636,7 @@ this.stopRecording = stopRecording; this.getRecording = getRecording; this.getRecordedTrack = getRecordedTrack; + this.getRecordedBackingTrack = getRecordedBackingTrack; this.getClaimedRecordings = getClaimedRecordings; this.getClaimedRecording = getClaimedRecording; this.updateClaimedRecording = updateClaimedRecording; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index feb1f2e28..74491f43c 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -39,6 +39,7 @@ var startTimeDate = null; var startingRecording = false; // double-click guard var claimedRecording = null; + var backing_track_path = null; var playbackControls = null; var promptLeave = false; var rateSessionDialog = null; @@ -435,6 +436,7 @@ var currentSession = sessionModel.getCurrentSession(); + if(claimedRecording == null && (currentSession && currentSession.claimed_recording != null)) { // this is a 'started with a claimed_recording' transition. // we need to start a timer to watch for the state of the play session @@ -443,10 +445,18 @@ else if(claimedRecording && (currentSession == null || currentSession.claimed_recording == null)) { playbackControls.stopMonitor(); } - claimedRecording = currentSession == null ? null : currentSession.claimed_recording; + + if(backing_track_path == null && (currentSession && currentSession.backing_track_path != null)) { + playbackControls.startMonitor(); + } + else if(backing_track_path && (currentSession == null || currentSession.backing_track_path == null)) { + playbackControls.stopMonitor(); + } + backing_track_path = currentSession == null ? null : currentSession.backing_track_path; } + function sessionChanged() { handleTransitionsInRecordingPlayback(); @@ -828,6 +838,9 @@ var localMediaMixers = _mixersForGroupIds([ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup], MIX_MODES.MASTER); var peerLocalMediaMixers = _mixersForGroupId(ChannelGroupIds.PeerMediaTrackGroup, MIX_MODES.MASTER); + var recordedBackingTracks = sessionModel.recordedBackingTracks(); + var backingTracks = sessionModel.backingTracks(); + // with mixer info, we use these to decide what kind of tracks are open in the backend // each mixer has a media_type field, which describes the type of media track it is. @@ -848,11 +861,41 @@ var metronomeTrackMixers = []; var adhocTrackMixers = []; - function groupByType(mixers) { + function groupByType(mixers, isLocalMixer) { context._.each(mixers, function(mixer) { var mediaType = mixer.media_type; var groupId = mixer.group_id; - if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { + + // mediaType == null is for backwards compat with older clients. Can be removed soon + if(mediaType == null || mediaType == "" || mediaType == 'RecordingTrack') { + // additional check; if we can match an id in backing tracks or recorded backing track, + // we need to remove it from the recorded track set, but move it to the backing track set + + var isBackingTrack = false + if(recordedBackingTracks) { + context._.each(recordedBackingTracks, function (recordedBackingTrack) { + if (mixer.id == 'L' + recordedBackingTrack.client_track_id) { + isBackingTrack = true; + return false; // break + } + }) + } + if(backingTracks) { + context._.each(backingTracks, function (backingTrack) { + if (mixer.id == 'L' + backingTrack.client_track_id) { + isBackingTrack = true; + return false; // break + } + }) + } + + if(isBackingTrack) { + backingTrackMixers.push(mixer) + } + else { + recordingTrackMixers.push(mixer); + } + } else if(mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack') { // BackingTrack backingTrackMixers.push(mixer); } else if(mediaType == 'MetronomeTrack' || groupId==ChannelGroupIds.MetronomeGroup) { @@ -871,8 +914,8 @@ }); } - groupByType(localMediaMixers); - groupByType(peerLocalMediaMixers); + groupByType(localMediaMixers, true); + groupByType(peerLocalMediaMixers, false); if(recordingTrackMixers.length > 0) { renderRecordingTracks(recordingTrackMixers) @@ -901,7 +944,7 @@ var backingTracks = [] if(sessionModel.isPlayingRecording()) { // only return managed mixers for recorded backing tracks - backingTrackMixers = context._.filter(backingTrackMixers, function(mixer){return mixer.managed}) + backingTrackMixers = context._.filter(backingTrackMixers, function(mixer){return mixer.managed || mixer.managed === undefined}) backingTracks = sessionModel.recordedBackingTracks(); } else { @@ -910,7 +953,7 @@ backingTrackMixers = context._.filter(backingTrackMixers, function(mixer){return !mixer.managed}) if(backingTrackMixers.length > 1) { app.notify({ - title: "Multiple Backing Tracks Encounterd", + title: "Multiple Backing Tracks Encountered", text: "Only one backing track can be open a time.", icon_url: "/assets/content/icon_alert_big.png" }); @@ -928,7 +971,8 @@ var noCorrespondingTracks = false; if(sessionModel.isPlayingRecording()) { $.each(backingTracks, function (i, backingTrack) { - if(mixer.persisted_track_id == backingTrack.client_track_id) { + if(mixer.persisted_track_id == backingTrack.client_track_id || // occurs if this client is the one that opened the track + mixer.id == 'L' + backingTrack.client_track_id) { // occurs if this client is a remote participant correspondingTracks.push(backingTrack) } }); @@ -941,6 +985,7 @@ if (correspondingTracks.length == 0) { noCorrespondingTracks = true; + logger.debug("renderBackingTracks: could not map backing tracks") app.notify({ title: "Unable to Open Backing Track", text: "Could not correlate server and client tracks", @@ -956,20 +1001,24 @@ // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener var isOpener = mixer.group_id == ChannelGroupIds.MediaTrackGroup; + var shortFilename = context.JK.getNameOfFile(backingTrack.filename); + if(!sessionModel.isPlayingRecording()) { // if a recording is being played back, do not set this header, because renderRecordedTracks already did // ugly. - $('.session-recording-name').text(backingTrack.filename); + $('.session-recording-name').text(shortFilename); } var instrumentIcon = context.JK.getInstrumentIcon45(backingTrack.instrument_id); var photoUrl = "/assets/content/icon_recording.png"; + // Default trackData to participant + no Mixer state. var trackData = { trackId: backingTrack.id, clientId: backingTrack.client_id, - name: backingTrack.filename, + name: 'Backing', + filename: backingTrack.filename, instrumentIcon: instrumentIcon, avatar: photoUrl, latency: "good", @@ -997,7 +1046,7 @@ trackData.mediaControlsDisabled = true; trackData.mediaTrackOpener = isOpener; } - _addRecordingTrack(trackData); + _addRecordingTrack(trackData, mixer); }); } @@ -1187,7 +1236,8 @@ //trackData.mediaControlsDisabled = true; trackData.mediaTrackOpener = isOpener; } - _addRecordingTrack(trackData); + + _addRecordingTrack(trackData, mixer); }// if setFormFromMetronome() } @@ -1196,6 +1246,7 @@ function renderRecordingTracks(recordingMixers) { // get the server's info for the recording var recordedTracks = sessionModel.recordedTracks(); + var recordedBackingTracks = sessionModel.recordedBackingTracks(); // pluck the 1st mixer, and assume that all other mixers in this group are of the same type (between Local vs Peer) // if it's a locally opened track (MediaTrackGroup), then we can say this person is the opener @@ -1231,6 +1282,7 @@ if(correspondingTracks.length == 0) { noCorrespondingTracks = true; + logger.debug("unable to correlate all recorded tracks", recordingMixers, recordedTracks) app.notify({ title: "Unable to Open Recording", text: "Could not correlate server and client tracks", @@ -1283,7 +1335,7 @@ trackData.mediaControlsDisabled = true; trackData.mediaTrackOpener = isOpener; } - _addRecordingTrack(trackData); + _addRecordingTrack(trackData, mixer); }); if(!noCorrespondingTracks && recordedTracks.length > 0) { @@ -1340,6 +1392,7 @@ var mixMode = sessionModel.getMixMode(); if(myTrack) { + // when it's your track, look it up by the backend resource ID mixer = getMixerByTrackId(track.client_track_id, mixMode) vuMixer = mixer; @@ -1697,7 +1750,7 @@ - function _addRecordingTrack(trackData) { + function _addRecordingTrack(trackData, mixer) { var parentSelector = '#session-recordedtracks-container'; var $destination = $(parentSelector); @@ -1716,6 +1769,8 @@ if(trackData.mediaControlsDisabled) { $trackIconMute.data('media-controls-disabled', true).data('media-track-opener', trackData.mediaTrackOpener) } + $trackIconMute.data('mixer', mixer).data('opposite-mixer', null) + if(trackData.showLoop) { var $trackIconLoop = $track.find('.track-icon-loop') @@ -1723,8 +1778,8 @@ var loopBtn = $trackIconLoop.find('#loop-button') loopBtn.on('click', function(event) { var loop = loopBtn.is(':checked') - console.log("Looping: ", loop); - context.jamClient.SessionSetBackingTrackFileLoop(trackData.name, loop) + logger.debug("Looping: ", loop, trackData.filename); + context.jamClient.SessionSetBackingTrackFileLoop(trackData.filename, loop) }) } @@ -1850,7 +1905,27 @@ } } + function handleBackingTrackSelectedCallback(result) { + if(result.success) { + logger.debug("backing track selected: " + result.file); + rest.openBackingTrack({id: context.JK.CurrentSessionModel.id(), backing_track_path: result.file}) + .done(function(response) { + var openResult = context.jamClient.SessionOpenBackingTrackFile(result.file, false); + console.log("BackingTrackPlay response: %o", openResult); + //context.JK.CurrentSessionModel.refreshCurrentSession(true); + sessionModel.setBackingTrack(result.file); + }) + .fail(function(jqXHR) { + app.notifyServerError(jqXHR, "Unable to Open BackingTrack For Playback"); + }) + + + } + else { + logger.debug("no backing track selected") + } + } function deleteSession(evt) { var sessionId = $(evt.currentTarget).attr("action-id"); if (sessionId) { @@ -2180,11 +2255,13 @@ return false; } - app.layout.showDialog('open-backing-track-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { - if(!data.cancel && data.result){ - sessionModel.setBackingTrack(data.result); - } - }) + context.jamClient.ShowSelectBackingTrackDialog("window.JK.HandleBackingTrackSelectedCallback"); + + //app.layout.showDialog('open-backing-track-dialog').one(EVENTS.DIALOG_CLOSED, function(e, data) { + // if(!data.cancel && data.result){ + // sessionModel.setBackingTrack(data.result); + // } + //}) return false; } @@ -2566,6 +2643,7 @@ context.JK.HandleVolumeChangeCallback = handleVolumeChangeCallback; context.JK.HandleMetronomeCallback = handleMetronomeCallback; context.JK.HandleBridgeCallback = handleBridgeCallback; + context.JK.HandleBackingTrackSelectedCallback = handleBackingTrackSelectedCallback; }; })(window,jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/sync_viewer.js.coffee b/web/app/assets/javascripts/sync_viewer.js.coffee index 3ba40ffb3..0bf819d2e 100644 --- a/web/app/assets/javascripts/sync_viewer.js.coffee +++ b/web/app/assets/javascripts/sync_viewer.js.coffee @@ -632,11 +632,11 @@ context.JK.SyncViewer = class SyncViewer uploadStateDefinition = switch uploadState when @uploadStates.too_many_upload_failures then "Failed attempts at uploading this backing track has happened an unusually large times. No more uploads will be attempted." - when @uploadStates.me_upload_soon then "PENDING UPLOAD means your JamKazam application will upload this track soon." - when @uploadStates.them_up_soon then "PENDING UPLOAD means #{serverInfo.user.name} will upload this track soon." - when @uploadStates.me_uploaded then "UPLOADED means you have already uploaded this track." - when @uploadStates.them_uploaded then "UPLOADED means #{serverInfo.user.name} has already uploaded this track." - when @uploadStates.missing then "MISSING means your JamKazam application does not have this track, and the server does not either." + when @uploadStates.me_upload_soon then "PENDING UPLOAD means your JamKazam application will upload this backing track soon." + when @uploadStates.them_up_soon then "PENDING UPLOAD means #{serverInfo.user.name} will upload this backing track soon." + when @uploadStates.me_uploaded then "UPLOADED means you have already uploaded this backing track." + when @uploadStates.them_uploaded then "UPLOADED means #{serverInfo.user.name} has already uploaded this backing track." + when @uploadStates.missing then "MISSING means your JamKazam application does not have this backing track, and the server does not either." when @uploadStates.not_mine then "NOT MINE means someone else opened and played this backing track." context._.template(@templateHoverRecordedBackingTrack.html(), @@ -709,6 +709,39 @@ context.JK.SyncViewer = class SyncViewer return false + retryDownloadRecordedBackingTrack: (e) => + $retry = $(e.target) + $track = $retry.closest('.recorded-backing-track') + serverInfo = $track.data('server-info') + + console.log("track serverInfo", $track, serverInfo) + this.sendCommand($retry, { + type: 'recorded_backing_track', + action: 'download' + queue: 'download', + recording_id: serverInfo.recording_id + track_id: serverInfo.client_track_id + }) + + return false + + retryUploadRecordedBackingTrack: (e) => + $retry = $(e.target) + $track = $retry.closest('.recorded-backing-track') + serverInfo = $track.data('server-info') + + console.log("track serverInfo", $track, serverInfo) + + this.sendCommand($retry, { + type: 'recorded_backing_track', + action: 'upload' + queue: 'upload', + recording_id: serverInfo.recording_id + track_id: serverInfo.client_track_id + }) + + return false + createMix: (userSync) => recordingInfo = null if userSync == 'fake' @@ -751,6 +784,24 @@ context.JK.SyncViewer = class SyncViewer $uploadState.addClass('is-native-client') if gon.isNativeClient $track + createBackingTrack: (userSync) => + $track = $(context._.template(@templateRecordedBackingTrack.html(), userSync, {variable: 'data'})) + $track.data('server-info', userSync) + $track.data('sync-viewer', this) + $clientState = $track.find('.client-state') + $uploadState = $track.find('.upload-state') + $clientStateRetry = $clientState.find('.retry') + $clientStateRetry.click(this.retryDownloadRecordedBackingTrack) + $uploadStateRetry = $uploadState.find('.retry') + $uploadStateRetry.click(this.retryUploadRecordedBackingTrack) + context.JK.bindHoverEvents($track) + context.JK.bindInstrumentHover($track, {positions:['top'], shrinkToFit: true}); + context.JK.hoverBubble($clientState, this.onBackingTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['left']}) + context.JK.hoverBubble($uploadState, this.onBackingTrackHoverOfStateIndicator, {width:'450px', closeWhenOthersOpen: true, positions:['right']}) + $clientState.addClass('is-native-client') if gon.isNativeClient + $uploadState.addClass('is-native-client') if gon.isNativeClient + $track + createStreamMix: (userSync) => $track = $(context._.template(@templateStreamMix.html(), userSync, {variable: 'data'})) $track.data('server-info', userSync) @@ -884,6 +935,8 @@ context.JK.SyncViewer = class SyncViewer for userSync in response.entries if userSync.type == 'recorded_track' @list.append(this.createTrack(userSync)) + if userSync.type == 'recorded_backing_track' + @list.append(this.createBackingTrack(userSync)) else if userSync.type == 'mix' @list.append(this.createMix(userSync)) else if userSync.type == 'stream_mix' @@ -904,6 +957,8 @@ context.JK.SyncViewer = class SyncViewer for track in @list.find('.recorded-track.sync') this.updateTrackState($(track)) + for track in @list.find('.recorded-backing-track.sync') + this.updateBackingTrackState($(track)) for streamMix in @list.find('.stream-mix.sync') this.updateStreamMixState($(streamMix)) @@ -923,6 +978,18 @@ context.JK.SyncViewer = class SyncViewer deferred.resolve(matchingTrack.data('server-info')) return deferred + resolveBackingTrack: (commandMetadata) => + recordingId = commandMetadata['recording_id'] + clientTrackId = commandMetadata['track_id'] + + matchingTrack = @list.find(".recorded-backing-track[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']") + if matchingTrack.length == 0 + return @rest.getRecordedBackingTrack({recording_id: recordingId, track_id: clientTrackId}) + else + deferred = $.Deferred(); + deferred.resolve(matchingTrack.data('server-info')) + return deferred + renderFullUploadRecordedTrack: (serverInfo) => $track = $(context._.template(@templateRecordedTrackCommand.html(), $.extend(serverInfo, {action:'UPLOADING'}), {variable: 'data'})) $busy = @uploadProgress.find('.busy') @@ -935,6 +1002,18 @@ context.JK.SyncViewer = class SyncViewer $busy.empty().append($track) @downloadProgress.find('.progress').css('width', '0%') + renderFullUploadRecordedBackingTrack: (serverInfo) => + $track = $(context._.template(@templateRecordedBackingTrackCommand.html(), $.extend(serverInfo, {action:'UPLOADING'}), {variable: 'data'})) + $busy = @uploadProgress.find('.busy') + $busy.empty().append($track) + @uploadProgress.find('.progress').css('width', '0%') + + renderFullDownloadRecordedBackingTrack: (serverInfo) => + $track = $(context._.template(@templateRecordedBackingTrackCommand.html(), $.extend(serverInfo, {action:'DOWNLOADING'}), {variable: 'data'})) + $busy = @downloadProgress.find('.busy') + $busy.empty().append($track) + @downloadProgress.find('.progress').css('width', '0%') + # this will either show a generic placeholder, or immediately show the whole track renderDownloadRecordedTrack: (commandId, commandMetadata) => # try to find the info in the list; if we can't find it, then resolve it @@ -953,6 +1032,23 @@ context.JK.SyncViewer = class SyncViewer deferred.done(this.renderFullUploadRecordedTrack).fail(()=> @logger.error("unable to fetch recorded_track info") ) + # this will either show a generic placeholder, or immediately show the whole track + renderDownloadRecordedBackingTrack: (commandId, commandMetadata) => + # try to find the info in the list; if we can't find it, then resolve it + deferred = this.resolveBackingTrack(commandMetadata) + if deferred.state() == 'pending' + this.renderGeneric(commandId, 'download', commandMetadata) + + deferred.done(this.renderFullDownloadRecordedBackingTrack).fail(()=> @logger.error("unable to fetch recorded_backing_track info") ) + + renderUploadRecordedBackingTrack: (commandId, commandMetadata) => + # try to find the info in the list; if we can't find it, then resolve it + deferred = this.resolveBackingTrack(commandMetadata) + if deferred.state() == 'pending' + this.renderGeneric(commandId, 'upload', commandMetadata) + + deferred.done(this.renderFullUploadRecordedBackingTrack).fail(()=> @logger.error("unable to fetch recorded_backing_track info") ) + renderGeneric: (commandId, category, commandMetadata) => commandMetadata.displayType = this.displayName(commandMetadata) @@ -991,6 +1087,8 @@ context.JK.SyncViewer = class SyncViewer @downloadProgress.addClass('busy') if commandMetadata.type == 'recorded_track' and commandMetadata.action == 'download' this.renderDownloadRecordedTrack(commandId, commandMetadata) + else if commandMetadata.type == 'recorded_backing_track' and commandMetadata.action == 'download' + this.renderDownloadRecordedBackingTrack(commandId, commandMetadata) else this.renderGeneric(commandId, 'download', commandMetadata) else if commandMetadata.queue == 'upload' @@ -1000,6 +1098,8 @@ context.JK.SyncViewer = class SyncViewer @uploadProgress.addClass('busy') if commandMetadata.type == 'recorded_track' and commandMetadata.action == 'upload' this.renderUploadRecordedTrack(commandId, commandMetadata) + else if commandMetadata.type == 'recorded_backing_track' and commandMetadata.action == 'upload' + this.renderUploadRecordedBackingTrack(commandId, commandMetadata) else this.renderGeneric(commandId, 'upload', commandMetadata) else if commandMetadata.queue == 'cleanup' @@ -1017,6 +1117,12 @@ context.JK.SyncViewer = class SyncViewer $track.data('server-info', userSync) this.associateClientInfo(clientRecordings.recordings[0]) this.updateTrackState($track) + else if userSync.type == 'recorded_backing_track' + $track = @list.find(".sync[data-id='#{userSync.id}']") + continue if $track.length == 0 + $track.data('server-info', userSync) + this.associateClientInfo(clientRecordings.recordings[0]) + this.updateBackingTrackState($track) else if userSync.type == 'mix' # check if there is a virtual mix 1st; if so, update it $mix = @list.find(".mix.virtual[data-recording-id='#{userSync.recording.id}']") @@ -1036,20 +1142,6 @@ context.JK.SyncViewer = class SyncViewer updateSingleRecording: (recording_id) => @rest.getUserSyncs({recording_id: recording_id}).done(this.renderSingleRecording) - updateSingleRecordedTrack: ($track) => - serverInfo = $track.data('server-info') - @rest.getUserSync({user_sync_id: serverInfo.id}) - .done((userSync) => - # associate new server-info with this track - $track.data('server-info', userSync) - # associate new client-info with this track - clientRecordings = context.jamClient.GetLocalRecordingState(recordings: [userSync.recording]) - this.associateClientInfo(clientRecordings.recordings[0]) - - this.updateTrackState($track) - ) - .fail(@app.ajaxError) - updateProgressOnSync: ($track, queue, percentage) => state = if queue == 'upload' then '.upload-state' else '.client-state' $progress = $track.find("#{state} .progress") @@ -1089,10 +1181,10 @@ context.JK.SyncViewer = class SyncViewer $progress = @downloadProgress.find('.progress') $progress.css('width', percentage + '%') - if @downloadMetadata.type == 'recorded_track' + if @downloadMetadata.type == 'recorded_track' or @downloadMetadata.type == 'recorded_backing_track' clientTrackId = @downloadMetadata['track_id'] recordingId = @downloadMetadata['recording_id'] - $matchingTrack = @list.find(".recorded-track.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']") + $matchingTrack = @list.find(".track-item.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']") if $matchingTrack.length > 0 this.updateProgressOnSync($matchingTrack, 'download', percentage) @@ -1100,10 +1192,10 @@ context.JK.SyncViewer = class SyncViewer $progress = @uploadProgress.find('.progress') $progress.css('width', percentage + '%') - if @uploadMetadata.type == 'recorded_track' and @uploadMetadata.action == 'upload' + if (@uploadMetadata.type == 'recorded_track' or @uploadMetadata.type == 'recorded_backing_track') and @uploadMetadata.action == 'upload' clientTrackId = @uploadMetadata['track_id'] recordingId = @uploadMetadata['recording_id'] - $matchingTrack = @list.find(".recorded-track.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']") + $matchingTrack = @list.find(".track-item.sync[data-recording-id='#{recordingId}'][data-client-track-id='#{clientTrackId}']") if $matchingTrack.length > 0 this.updateProgressOnSync($matchingTrack, 'upload', percentage) else if @uploadMetadata.type == 'stream_mix' and @uploadMetadata.action == 'upload' @@ -1174,15 +1266,15 @@ context.JK.SyncViewer = class SyncViewer this.logResult(data.commandMetadata, false, data.commandReason, true) displayName: (metadata) => - if metadata.type == 'recorded_track' && metadata.action == 'download' + if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'download' return 'DOWNLOADING TRACK' - else if metadata.type == 'recorded_track' && metadata.action == 'upload' + else if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'upload' return 'UPLOADING TRACK' else if metadata.type == 'mix' && metadata.action == 'download' return 'DOWNLOADING MIX' - else if metadata.type == 'recorded_track' && metadata.action == 'convert' + else if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'convert' return 'COMPRESSING TRACK' - else if metadata.type == 'recorded_track' && metadata.action == 'delete' + else if (metadata.type == 'recorded_track' || metadata.type == 'recorded_backing_track') && metadata.action == 'delete' return 'CLEANUP TRACK' else if metadata.type == 'stream_mix' && metadata.action == 'upload' return 'UPLOADING STREAM MIX' diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 052b7d20e..1b6c1a575 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -987,6 +987,15 @@ return hasFlash; } + context.JK.getNameOfFile = function(filename) { + + var index = filename.lastIndexOf('/'); + if(index == -1) { + index = filename.lastIndexOf('\\'); + } + return index == -1 ? filename : filename.substring(index + 1, filename.length) + } + context.JK.hasOneConfiguredDevice = function () { var result = context.jamClient.FTUEGetGoodConfigurationList(); logger.debug("hasOneConfiguredDevice: ", result); diff --git a/web/app/assets/stylesheets/client/common.css.scss b/web/app/assets/stylesheets/client/common.css.scss index e2f174fe0..a349729f5 100644 --- a/web/app/assets/stylesheets/client/common.css.scss +++ b/web/app/assets/stylesheets/client/common.css.scss @@ -208,6 +208,10 @@ $fair: #cc9900; background-color: $error; } + &.not_mine { + background-color: $good; + } + &.discarded { background-color: $unknown; } @@ -252,6 +256,10 @@ $fair: #cc9900; background-color: $good; } + &.not_mine { + background-color: $good; + } + .retry { display:none; position:absolute; diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 842a9d361..5caa975a8 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -44,6 +44,10 @@ class ApiRecordingsController < ApiController @recorded_track = RecordedTrack.find_by_recording_id_and_client_track_id(params[:id], params[:track_id]) end + def show_recorded_backing_track + @recorded_backing_track = RecordedBackingTrack.find_by_recording_id_and_client_track_id(params[:id], params[:track_id]) + end + def download # track raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_track.can_download?(current_user) diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl index b038a0102..aa7de2609 100644 --- a/web/app/views/api_claimed_recordings/show.rabl +++ b/web/app/views/api_claimed_recordings/show.rabl @@ -45,8 +45,8 @@ child(:recording => :recording) { attributes :id, :first_name, :last_name, :name, :city, :state, :country, :location, :photo_url } - node :mine do |user| - user == current_user + node :mine do |recorded_backing_track| + recorded_backing_track.user == current_user end } diff --git a/web/app/views/api_recordings/show_recorded_backing_track.rabl b/web/app/views/api_recordings/show_recorded_backing_track.rabl index 1c9e1a078..6487b9db6 100644 --- a/web/app/views/api_recordings/show_recorded_backing_track.rabl +++ b/web/app/views/api_recordings/show_recorded_backing_track.rabl @@ -2,6 +2,10 @@ object @recorded_backing_track attributes :id, :fully_uploaded, :client_track_id, :client_id, :recording_id, :filename +node :mine do |recorded_backing_track| + recorded_backing_track.user == current_user + end + child(:user => :user) { attributes :id, :first_name, :last_name, :city, :state, :country, :location, :photo_url } \ No newline at end of file diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index 452d88809..f43452f7c 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -94,9 +94,10 @@ a#open-a-backingtrack[href="#"] | Audio File .when-empty.use-metronome-header - = image_tag "content/icon_metronome.png", {width:22, height:20} - a#open-a-metronome[href="#"] - | Use Metronome + - if Rails.application.config.metronome_available + = image_tag "content/icon_metronome.png", {width:22, height:20} + a#open-a-metronome[href="#"] + | Use Metronome br[clear="all"] = render "play_controls" = render "configureTrack" diff --git a/web/app/views/clients/_sync_viewer_templates.html.slim b/web/app/views/clients/_sync_viewer_templates.html.slim index 92f0953bd..35640dfe4 100644 --- a/web/app/views/clients/_sync_viewer_templates.html.slim +++ b/web/app/views/clients/_sync_viewer_templates.html.slim @@ -60,7 +60,7 @@ script type="text/template" id='template-sync-viewer-recorded-track' = image_tag('content/icon_resync.png', width:12, height: 14) script type="text/template" id='template-sync-viewer-recorded-backing-track' - .recorded-track.sync data-id="{{data.id}}" data-recording-id="{{data.recording_id}}" data-client-id="{{data.client_id}}" data-client-track-id="{{data.client_track_id}}" data-track-id="{{data.backing_track_id}}" data-fully-uploaded="{{data.fully_uploaded}}" + .recorded-backing-track.sync data-id="{{data.id}}" data-recording-id="{{data.recording_id}}" data-client-id="{{data.client_id}}" data-client-track-id="{{data.client_track_id}}" data-track-id="{{data.backing_track_id}}" data-fully-uploaded="{{data.fully_uploaded}}" .type span.text BACKING a.avatar-tiny href="#" user-id="{{data.user.id}}" hoveraction="musician" @@ -233,7 +233,7 @@ script type="text/template" id="template-sync-viewer-generic-command" | {{data.displayType}} script type="text/template" id="template-sync-viewer-recorded-track-command" - .recorded-track.sync + .recorded-track.track-item.sync .type .progress span.text @@ -243,7 +243,7 @@ script type="text/template" id="template-sync-viewer-recorded-track-command" img.instrument-icon data-instrument-id="{{data.instrument_id}}" hoveraction="instrument" src="{{JK.getInstrumentIconMap24()[data.instrument_id].asset}}" script type="text/template" id="template-sync-viewer-recorded-backing-track-command" - .recorded-backing-track.sync + .recorded-backing-track.track-item.sync .type .progress span.text diff --git a/web/config/application.rb b/web/config/application.rb index 1d76163fd..39e5b6a40 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -312,5 +312,6 @@ if defined?(Bundler) config.show_jamblaster_notice = true config.show_jamblaster_kickstarter_link = true + config.metronome_available = true end end diff --git a/web/config/routes.rb b/web/config/routes.rb index d50d69a4c..e018686e1 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -464,6 +464,7 @@ SampleApp::Application.routes.draw do match '/recordings/:id/stream_mix/upload_next_part' => 'api_recordings#upload_next_part_stream_mix', :via => :get # Recordings - backing tracks + match '/recordings/:id/backing_tracks/:track_id' => 'api_recordings#show_recorded_backing_track', :via => :get, :as => 'api_recordings_show_recorded_backing_track' match '/recordings/:id/backing_tracks/:track_id/download' => 'api_recordings#backing_track_download', :via => :get, :as => 'api_recordings_download' match '/recordings/:id/backing_tracks/:track_id/upload_next_part' => 'api_recordings#backing_track_upload_next_part', :via => :get match '/recordings/:id/backing_tracks/:track_id/upload_sign' => 'api_recordings#backing_track_upload_sign', :via => :get From e40c964d97d9df85501c53b22fbc0503c1a804b2 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 15 Feb 2015 22:15:34 -0600 Subject: [PATCH 43/46] * merged --- .../jam_ruby/models/recorded_backing_track.rb | 7 +++++++ ruby/lib/jam_ruby/models/recorded_track.rb | 2 +- .../dialog/recordingFinishedDialog.js | 17 +++++++++++++++++ web/app/assets/javascripts/jam_rest.js | 13 +++++++++++++ web/app/assets/javascripts/session.js | 3 ++- .../api_backing_tracks_controller.rb | 17 ++++++++++++++++- web/config/routes.rb | 1 + 7 files changed, 57 insertions(+), 3 deletions(-) diff --git a/ruby/lib/jam_ruby/models/recorded_backing_track.rb b/ruby/lib/jam_ruby/models/recorded_backing_track.rb index f37659d3e..42513620c 100644 --- a/ruby/lib/jam_ruby/models/recorded_backing_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_backing_track.rb @@ -179,6 +179,13 @@ module JamRuby s3_manager.delete(self[:url]) if self[:url] && s3_manager.exists?(self[:url]) end + def mark_silent + destroy + + # check if we have all the files we need, now that the recorded_backing_track is out of the way + recording.preconditions_for_mix? + end + private def self.construct_filename(created_at, recording_id, client_track_id) diff --git a/ruby/lib/jam_ruby/models/recorded_track.rb b/ruby/lib/jam_ruby/models/recorded_track.rb index 2a6a828d1..e11a76bf0 100644 --- a/ruby/lib/jam_ruby/models/recorded_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_track.rb @@ -228,7 +228,7 @@ module JamRuby def self.construct_filename(created_at, recording_id, client_track_id) raise "unknown ID" unless client_track_id - "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/backing-track-#{client_track_id}.ogg" + "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/track-#{client_track_id}.ogg" end end end diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js index f0d24a27d..41eec0101 100644 --- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js +++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js @@ -78,6 +78,23 @@ "icon_url": "/assets/content/icon_alert_big.png" }); } + else { + // hunt for missing backing tracks; if so, mark them as silent + context._.each(openRecordingResult.backing_tracks, function(backingTrack) { + if(backingTrack.local_state == "MISSING") { + // mark this as deleted + logger.debug("marking recorded track as deleted") + rest.markRecordedBackingTrackSilent({recording_id: openRecordingResult.recording_id, backing_track_id: backingTrack.client_track_id}) + .fail(function() { + app.notify({ + "title": "Unable to Mark Backing Track", + "text": "A backing track was never played, but we could not tell the server to remove it from the recording.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + }) + } + }) + } playbackControls.startMonitor(); } diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index 76a260b72..f0500ae65 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -1043,6 +1043,18 @@ }) } + function markRecordedBackingTrackSilent(options) { + var recordingId = options["recording_id"]; + var trackId = options["backing_track_id"]; + + return $.ajax({ + type: "POST", + dataType: "json", + contentType: 'application/json', + data: {}, + url: "/api/recordings/" + recordingId + "/backing_tracks/" + trackId + '/silent' + }); + } function getRecordedTrack(options) { var recordingId = options["recording_id"]; var trackId = options["track_id"]; @@ -1697,6 +1709,7 @@ this.getMount = getMount; this.createSourceChange = createSourceChange; this.validateUrlSite = validateUrlSite; + this.markRecordedBackingTrackSilent = markRecordedBackingTrackSilent; return this; }; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 74491f43c..0f6a7d841 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -908,7 +908,7 @@ // mediaType == null is for backwards compat with older clients. Can be removed soon recordingTrackMixers.push(mixer) } else { - console.log("Unknown track type: " + mediaType) + logger.warn("Unknown track type: " + mediaType) adhocTrackMixers.push(mixer); } }); @@ -1257,6 +1257,7 @@ if(recordedTracks) { $('.session-recording-name').text(sessionModel.getCurrentSession().claimed_recording.name); + console.log("renderRecordingTracks", recordingMixers, recordedTracks) var noCorrespondingTracks = false; $.each(recordingMixers, function(index, mixer) { var preMasteredClass = ""; diff --git a/web/app/controllers/api_backing_tracks_controller.rb b/web/app/controllers/api_backing_tracks_controller.rb index 618937052..9ed591422 100644 --- a/web/app/controllers/api_backing_tracks_controller.rb +++ b/web/app/controllers/api_backing_tracks_controller.rb @@ -2,6 +2,8 @@ class ApiBackingTracksController < ApiController # have to be signed in currently to see this screen before_filter :api_signed_in_user + + before_filter :lookup_recorded_backing_track, :only => [ :backing_track_silent ] respond_to :json @@ -14,4 +16,17 @@ class ApiBackingTracksController < ApiController render "api_backing_tracks/index", :layout => nil end -end # class ApiJamTracksController + def backing_track_silent + @recorded_backing_track.mark_silent + + render :json => {}, :status => 200 + end + + private + + def lookup_recorded_backing_track + @recorded_backing_track = RecordedBackingTrack.find_by_recording_id_and_client_track_id!(params[:id], params[:track_id]) + raise PermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR unless @recorded_backing_track.recording.has_access?(current_user) + end + +end # class ApiBackingTracksController diff --git a/web/config/routes.rb b/web/config/routes.rb index e018686e1..2a0001179 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -470,6 +470,7 @@ SampleApp::Application.routes.draw do match '/recordings/:id/backing_tracks/:track_id/upload_sign' => 'api_recordings#backing_track_upload_sign', :via => :get match '/recordings/:id/backing_tracks/:track_id/upload_part_complete' => 'api_recordings#backing_track_upload_part_complete', :via => :post match '/recordings/:id/backing_tracks/:track_id/upload_complete' => 'api_recordings#backing_track_upload_complete', :via => :post + match '/recordings/:id/backing_tracks/:track_id/silent' => 'api_backing_tracks#backing_track_silent', :via => :post # Recordings - recorded_videos match '/recordings/:id/tracks/:video_id/upload_sign' => 'api_recordings#video_upload_sign', :via => :get From bdc1b898b443368faf16484c548b6b5a3532c7d8 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 14 Feb 2015 15:57:12 -0600 Subject: [PATCH 44/46] * VRFS-2780 - styling complete; needs testing --- web/app/assets/javascripts/session.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 0f6a7d841..edc3c449e 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -1024,7 +1024,7 @@ latency: "good", gainPercent: 0, muteClass: 'muted', - showLoop: !sessionModel.isPlayingRecording(), + showLoop: isOpener && !sessionModel.isPlayingRecording(), mixerId: "", avatarClass: 'avatar-recording', preMasteredClass: "" @@ -1775,13 +1775,15 @@ if(trackData.showLoop) { var $trackIconLoop = $track.find('.track-icon-loop') - $trackIconLoop.show() - var loopBtn = $trackIconLoop.find('#loop-button') - loopBtn.on('click', function(event) { - var loop = loopBtn.is(':checked') - logger.debug("Looping: ", loop, trackData.filename); - context.jamClient.SessionSetBackingTrackFileLoop(trackData.filename, loop) - }) + var $trackIconLoopCheckbox = $trackIconLoop.find('input') + + context.JK.checkbox($trackIconLoopCheckbox) + $trackIconLoopCheckbox.on('ifChanged', function() { + var loop = $trackIconLoopCheckbox.is(':checked') + logger.debug("Looping: ", loop, trackData.filename); + context.jamClient.SessionSetBackingTrackFileLoop(trackData.filename, loop) + }); + $trackIconLoop.show() } // is this used? From bfc03fba4860c3802f7c5910910b4aa37bbf9a6d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 16 Feb 2015 10:32:23 -0600 Subject: [PATCH 45/46] * VRFS-2780 - finish styling loop checkbox --- web/app/assets/javascripts/fakeJamClient.js | 9 +-------- web/app/assets/stylesheets/client/session.css.scss | 6 +++++- web/spec/features/in_session_spec.rb | 1 + 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 5547bc111..e571142fb 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -473,14 +473,7 @@ "FW AP Multi_2_10200", "FW AP Multi_3_10500", "User@208.191.152.98#", - "User@208.191.152.98_*", - "", - "", - "", - "", - "", - "", - "", + "User@208.191.152.98_*" ]; } diff --git a/web/app/assets/stylesheets/client/session.css.scss b/web/app/assets/stylesheets/client/session.css.scss index 14f232941..15b4a2522 100644 --- a/web/app/assets/stylesheets/client/session.css.scss +++ b/web/app/assets/stylesheets/client/session.css.scss @@ -538,13 +538,17 @@ table.vu td { cursor: pointer; position:absolute; top:250px; - left:9px; + left:11px; width: 20px; height: 18px; text-align: center; font-size: 8pt; font-weight: bold; + .icheckbox_minimal { + top:5px; + margin-right:5px; + } } .metronome-selects { diff --git a/web/spec/features/in_session_spec.rb b/web/spec/features/in_session_spec.rb index b9b5b7296..ef96b1deb 100644 --- a/web/spec/features/in_session_spec.rb +++ b/web/spec/features/in_session_spec.rb @@ -112,6 +112,7 @@ describe "In a Session", :js => true, :type => :feature, :capybara_feature => tr end specify "metronome" do + pending "working through issue with fake media tracks" user = FactoryGirl.create(:user) invitee = FactoryGirl.create(:user) FactoryGirl.create(:friendship, :user => user, :friend => invitee) From 51689e3d6105bdcdc14f5219bdc52554e093dd8d Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 16 Feb 2015 14:59:58 -0600 Subject: [PATCH 46/46] * make backing tracks show on/off and fix migration --- db/manifest | 8 ++++---- web/app/views/clients/_session.html.slim | 7 ++++--- web/config/application.rb | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/db/manifest b/db/manifest index 0e81a7c5f..212832f28 100755 --- a/db/manifest +++ b/db/manifest @@ -243,12 +243,12 @@ bpms_on_tap_in.sql jamtracks_job.sql text_messages.sql text_message_migration.sql -backing_tracks.sql -metronome.sql -recorded_backing_tracks.sql user_model_about_changes.sql performance_samples.sql user_presences.sql -recorded_backing_tracks_add_filename.sql discard_scores_optimized.sql +backing_tracks.sql +metronome.sql +recorded_backing_tracks.sql +recorded_backing_tracks_add_filename.sql user_syncs_include_backing_tracks.sql \ No newline at end of file diff --git a/web/app/views/clients/_session.html.slim b/web/app/views/clients/_session.html.slim index f43452f7c..7a79b81fd 100644 --- a/web/app/views/clients/_session.html.slim +++ b/web/app/views/clients/_session.html.slim @@ -90,9 +90,10 @@ li a#open-a-jamtrack[href="#"] | JamTrack - li - a#open-a-backingtrack[href="#"] - | Audio File + - if Rails.application.config.backing_tracks_available + li + a#open-a-backingtrack[href="#"] + | Audio File .when-empty.use-metronome-header - if Rails.application.config.metronome_available = image_tag "content/icon_metronome.png", {width:22, height:20} diff --git a/web/config/application.rb b/web/config/application.rb index 39e5b6a40..687b93ff4 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -313,5 +313,6 @@ if defined?(Bundler) config.show_jamblaster_notice = true config.show_jamblaster_kickstarter_link = true config.metronome_available = true + config.backing_tracks_available = true end end