diff --git a/db/manifest b/db/manifest index 53fd01705..a59c77da4 100755 --- a/db/manifest +++ b/db/manifest @@ -359,4 +359,5 @@ lesson_time_tracking.sql packaged_test_drive.sql packaged_test_drive2.sql jamclass_report.sql -jamblasters_network.sql \ No newline at end of file +jamblasters_network.sql +immediate_recordings.sql \ No newline at end of file diff --git a/db/up/immediate_recordings.sql b/db/up/immediate_recordings.sql new file mode 100644 index 000000000..00b21af01 --- /dev/null +++ b/db/up/immediate_recordings.sql @@ -0,0 +1 @@ +ALTER TABLE recordings ADD COLUMN immediate BOOLEAN DEFAULT FALSE; \ No newline at end of file diff --git a/ruby/lib/jam_ruby/models/quick_mix.rb b/ruby/lib/jam_ruby/models/quick_mix.rb index 4053fdaa3..a9b66b76f 100644 --- a/ruby/lib/jam_ruby/models/quick_mix.rb +++ b/ruby/lib/jam_ruby/models/quick_mix.rb @@ -135,7 +135,7 @@ module JamRuby mix.recording = recording mix.user = user mix.save - mix[:ogg_url] = construct_filename(mix.created_at, recording.id, mix.id, type='ogg') + mix[:ogg_url] = construct_filename(mix.created_at, recording.id, mix.id, mix.default_type) mix[:mp3_url] = construct_filename(mix.created_at, recording.id, mix.id, type='mp3') mix.save mix.is_skip_mount_uploader = false @@ -205,8 +205,10 @@ module JamRuby end end - def s3_url(type='ogg') - if type == 'ogg' + def s3_url(type=default_type) + if type == 'aac' + s3_manager.s3_url(self[:ogg_url]) + elsif type == 'ogg' s3_manager.s3_url(self[:ogg_url]) else s3_manager.s3_url(self[:mp3_url]) @@ -222,10 +224,12 @@ module JamRuby self[url_field].start_with?('http') ? self[url_field] : s3_manager.sign_url(self[url_field], {:expires => expiration_time, :response_content_type => mime_type, :secure => true}) end - def sign_url(expiration_time = 120, type='ogg') + def sign_url(expiration_time = 120, type=default_type) type ||= 'ogg' # expire link in 1 minute--the expectation is that a client is immediately following this link - if type == 'ogg' + if type == 'aac' + resolve_url(:ogg_url, 'audio/aac', expiration_time) + elsif type == 'ogg' resolve_url(:ogg_url, 'audio/ogg', expiration_time) else resolve_url(:mp3_url, 'audio/mpeg', expiration_time) @@ -233,9 +237,11 @@ module JamRuby end # this is not 'secure' because, in testing, the PUT failed often in Ruby. should investigate more. - def sign_put(expiration_time = 3600 * 24, type='ogg') + def sign_put(expiration_time = 3600 * 24, type=default_type) type ||= 'ogg' - if type == 'ogg' + if type == 'aac' + s3_manager.sign_url(self[:ogg_url], {:expires => expiration_time, :content_type => 'audio/aac', :secure => false}, :put) + elsif type == 'ogg' s3_manager.sign_url(self[:ogg_url], {:expires => expiration_time, :content_type => 'audio/ogg', :secure => false}, :put) else s3_manager.sign_url(self[:mp3_url], {:expires => expiration_time, :content_type => 'audio/mpeg', :secure => false}, :put) @@ -258,19 +264,25 @@ module JamRuby end end - def filename(type='ogg') + def default_type + recording.immediate ? 'aac' : 'ogg' + end + + def filename(type=default_type) # construct a path for s3 QuickMix.construct_filename(self.created_at, self.recording_id, self.id, type) end def delete_s3_files - s3_manager.delete(filename(type='ogg')) if self[:ogg_url] && s3_manager.exists?(filename(type='ogg')) - s3_manager.delete(filename(type='mp3')) if self[:mp3_url] && s3_manager.exists?(filename(type='mp3')) + s3_manager.delete(filename(type=default_type)) if self[:ogg_url] && s3_manager.exists?(filename(type=default_type)) + s3_manager.delete(filename(type='mp3')) if self[:mp3_url] && s3_manager.exists?(filename(type=default_type)) end def self.construct_filename(created_at, recording_id, id, type='ogg') raise "unknown ID" unless id + "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/stream-mix-#{id}.#{type}" + 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 62fdfa1e5..df07f2366 100644 --- a/ruby/lib/jam_ruby/models/recorded_track.rb +++ b/ruby/lib/jam_ruby/models/recorded_track.rb @@ -147,6 +147,24 @@ module JamRuby recorded_track end + def self.create_for_immediate(user, recording) + recorded_track = self.new + recorded_track.recording = recording + recorded_track.client_id = 'ios' + recorded_track.track_id = 'ios' + recorded_track.client_track_id = 'ios' + recorded_track.user = user + recorded_track.instrument = Instrument.find('other') + recorded_track.sound = 'stereo' + recorded_track.next_part_to_upload = 0 + recorded_track.file_offset = 0 + recorded_track.is_skip_mount_uploader = true + recorded_track.save + recorded_track.url = construct_filename(recording.created_at, recording.id, 'ios', 'aac') + recorded_track.save + recorded_track.is_skip_mount_uploader = false + recorded_track + end def sign_url(expiration_time = 120) s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => true}) end @@ -226,9 +244,9 @@ module JamRuby private - def self.construct_filename(created_at, recording_id, client_track_id) + def self.construct_filename(created_at, recording_id, client_track_id, type='ogg') 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}/track-#{client_track_id}.#{type}" end end end diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb index c9550ba1a..b88153f80 100644 --- a/ruby/lib/jam_ruby/models/recording.rb +++ b/ruby/lib/jam_ruby/models/recording.rb @@ -132,6 +132,7 @@ module JamRuby end end + # this should be used to cleanup a recording that we detect is no longer running def abort recording.music_session.claimed_recording_id = nil @@ -212,6 +213,44 @@ module JamRuby ChatMessage.joins(:claimed_recording => [:recording]).where('recordings.id = ?', self.id).where('chat_messages.target_user_id = ?', user.id).count > 0 end + # creates a recording, and then claims it in one shot. + def self.create_immediately(owner, params) + recording = Recording.new + recording.music_session = nil + recording.owner = owner + recording.band = nil + recording.immediate = true # immediate in practice means 'the ios app uploaded this' + recording.video = params[:record_video] + + if recording.save + QuickMix.create(recording, owner) + + recording.recorded_tracks << RecordedTrack.create_for_immediate(owner, recording) + + recording.save + end + + if recording.errors.any? + return recording + end + + recording.stop + + if recording.errors.any? + return recording + end + + recording.reload + claim = recording.claim(owner, params[:name], params[:description], Genre.find_by_id(params[:genre]), params[:is_public], upload_to_youtube = params[:upload_to_youtube]) + + if claim.errors.any? + return claim + end + + recording + end + + # Start recording a session. def self.start(music_session, owner, record_video: false) recording = nil @@ -277,7 +316,7 @@ module JamRuby # Called when a user wants to "claim" a recording. To do this, the user must have been one of the tracks in the recording. def claim(user, name, description, genre, is_public, upload_to_youtube=false) upload_to_youtube = !!upload_to_youtube # Correct where nil is borking save - unless self.users.exists?(user) + if !self.users.exists?(user) raise JamPermissionError, "user was not in this session" end diff --git a/ruby/lib/jam_ruby/resque/quick_mixer.rb b/ruby/lib/jam_ruby/resque/quick_mixer.rb index 4412e0b0c..fe91996ba 100644 --- a/ruby/lib/jam_ruby/resque/quick_mixer.rb +++ b/ruby/lib/jam_ruby/resque/quick_mixer.rb @@ -129,7 +129,7 @@ module JamRuby def fetch_audio_files - @input_ogg_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/quick_mixer_#{@quick_mix.id}}", '.ogg'], nil) + @input_ogg_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/quick_mixer_#{@quick_mix.id}}", '.' + @quick_mix.default_type], nil) @output_mp3_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/quick_mixer_#{@quick_mix.id}}", '.mp3'], nil) @s3_manager.download(@quick_mix[:ogg_url], @input_ogg_filename) end diff --git a/ruby/spec/jam_ruby/models/recording_spec.rb b/ruby/spec/jam_ruby/models/recording_spec.rb index 3de7d09e1..d5c193e56 100644 --- a/ruby/spec/jam_ruby/models/recording_spec.rb +++ b/ruby/spec/jam_ruby/models/recording_spec.rb @@ -8,13 +8,28 @@ describe Recording do let(:s3_manager) { S3Manager.new(app_config.aws_bucket, app_config.aws_access_key_id, app_config.aws_secret_access_key) } before do - @user = FactoryGirl.create(:user) + @user = FactoryGirl.create(:user) @instrument = FactoryGirl.create(:instrument, :description => 'a great instrument') @music_session = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) @connection = FactoryGirl.create(:connection, :user => @user, :music_session => @music_session) - @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) + @track = FactoryGirl.create(:track, :connection => @connection, :instrument => @instrument) end + describe "create_immediately" do + it "success with no video" do + recording = Recording.create_immediately(@user, {name: 'My recording', description: nil, genre: Genre.first, record_video: false, is_public: true, upload_to_youtube: false}) + puts "recording errros #{recording.errors.inspect}" + recording.errors.any?.should be_false + recording.reload + recording.claimed_recordings.count.should eql 1 + claim = recording.claimed_recordings.first + claim.name.should eql 'My recording' + claim.description.should be_nil + claim.discarded.should be_false + claim.user.should eql @user + recording.owner.should eql @user + end + end describe "popular_recordings" do it "empty" do Recording.popular_recordings.length.should eq(0) @@ -43,13 +58,13 @@ describe Recording do sample_audio='sample.file' in_directory_with_file(sample_audio) - let(:user2) {FactoryGirl.create(:user)} + let(:user2) { FactoryGirl.create(:user) } let(:quick_mix) { FactoryGirl.create(:quick_mix) } let(:quick_mix2) { recording.recorded_tracks << FactoryGirl.create(:recorded_track, recording: recording, user: user2) recording.claimed_recordings << FactoryGirl.create(:claimed_recording, user: user2, recording: recording) recording.save! - FactoryGirl.create(:quick_mix, autowire: false, recording:recording, user: user2) } + FactoryGirl.create(:quick_mix, autowire: false, recording: recording, user: user2) } let(:recording) { quick_mix.recording } before(:each) do @@ -248,20 +263,20 @@ describe Recording do 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 @recording = Recording.start(@music_session, @user) @music_session.reload @music_session.recordings[0].should == @recording @recording.owner_id.should == @user.id - + @recorded_tracks = RecordedTrack.where(:recording_id => @recording.id) @recorded_tracks.length.should == 1 @recorded_tracks.first.instrument_id == @track.instrument_id - @recorded_tracks.first.user_id == @track.connection.user_id - end - + @recorded_tracks.first.user_id == @track.connection.user_id + end + it "should not start a recording if the session is already being recorded" do Recording.start(@music_session, @user).errors.any?.should be_false recording = Recording.start(@music_session, @user) @@ -299,7 +314,7 @@ describe Recording do @connection2 = FactoryGirl.create(:connection, :user => @user2) @instrument2 = FactoryGirl.create(:instrument, :description => 'a great instrument') @track2 = FactoryGirl.create(:track, :connection => @connection2, :instrument => @instrument2) - + # @music_session.connections << @connection2 @connection2.join_the_session(@music_session, true, nil, @user2, 10) @@ -310,7 +325,7 @@ describe Recording do @user2.recordings.length.should == 0 #@user2.recordings.first.should == @recording end - + it "should report correctly whether its tracks have been uploaded" do @recording = Recording.start(@music_session, @user) @recording.uploaded?.should == false @@ -320,7 +335,7 @@ describe Recording do @recording.recorded_tracks.first.fully_uploaded = true @recording.uploaded?.should == true end - + it "should destroy a recording and all its recorded tracks properly" do @recording = Recording.start(@music_session, @user) @recording.stop @@ -331,7 +346,7 @@ describe Recording do expect { RecordedTracks.find(@recorded_track.id) }.to raise_error end - it "should allow a user to claim a recording" do + it "should allow a user to claim a recording" do @recording = Recording.start(@music_session, @user) @recording.stop @recording.reload @@ -370,7 +385,7 @@ describe Recording do @claimed_recording.reload @claimed_recording.name.should == "name2" @claimed_recording.description.should == "description2" - @claimed_recording.genre.should == @genre2 + @claimed_recording.genre.should == @genre2 @claimed_recording.is_public.should == false @claimed_recording.upload_to_youtube.should == false end @@ -419,15 +434,15 @@ describe Recording do @recording.all_discarded.should == false end - it "should set youtube flag" do - + it "should set youtube flag" do + @connection.join_the_session(@music_session, true, nil, @user, 10) @recording = Recording.start(@music_session, @user) @recording.stop @recording.reload @genre = FactoryGirl.create(:genre) @recording.claim(@user, "name", "description", @genre, true, true) - + @recording.reload @recording.users.length.should == 1 @recording.users.first.should == @user @@ -570,7 +585,7 @@ describe Recording do @music_session.reload @music_session.recordings[0].should == @recording @recording.owner_id.should == @user.id - + @recorded_videos = RecordedVideo.where(:recording_id => @recording.id) @recorded_videos.should have(1).items @recorded_videos[0].client_video_source_id.should eq(@video_source.client_video_source_id) @@ -628,11 +643,11 @@ describe Recording do end it "should only retrieve tracks and videos from user" do - user2 = FactoryGirl.create(:user) # in the jam session + user2 = FactoryGirl.create(:user) # in the jam session music_session2 = FactoryGirl.create(:active_music_session, :creator => @user, :musician_access => true) - music_session_member2 = FactoryGirl.create(:connection, :user => user2, :music_session => @music_session, :ip_address => "2.2.2.2", :client_id => "2") + music_session_member2 = FactoryGirl.create(:connection, :user => user2, :music_session => @music_session, :ip_address => "2.2.2.2", :client_id => "2") connection2 = FactoryGirl.create(:connection, :user => user2, :music_session => @music_session) - track2 = FactoryGirl.create(:track, :connection => connection2, :instrument => @instrument) + track2 = FactoryGirl.create(:track, :connection => connection2, :instrument => @instrument) video_source = FactoryGirl.create(:video_source, :connection => @connection) video_source2 = FactoryGirl.create(:video_source, :connection => connection2) @@ -707,8 +722,8 @@ describe Recording do end describe "discarded_and_stale" do - let(:recording1) {FactoryGirl.create(:recording_with_track)} - let(:track1) {recording1.recorded_tracks[0]} + let(:recording1) { FactoryGirl.create(:recording_with_track) } + let(:track1) { recording1.recorded_tracks[0] } it "no results if no recordings" do Recording.discarded_and_stale.length.should == 0 @@ -913,8 +928,8 @@ describe Recording do end describe "two recordings" do - let(:recording2) {FactoryGirl.create(:recording_with_track)} - let(:track2) {recording2.recorded_tracks[0]} + let(:recording2) { FactoryGirl.create(:recording_with_track) } + let(:track2) { recording2.recorded_tracks[0] } describe "both discard" do @@ -1050,17 +1065,17 @@ describe Recording do end describe "delete" do - let(:mix) {FactoryGirl.create(:mix)} - let(:recording) {mix.recording} + let(:mix) { FactoryGirl.create(:mix) } + let(:recording) { mix.recording } before(:each) do end it "success" do - FactoryGirl.create(:quick_mix, user: recording.owner, recording:recording, autowire: false) + FactoryGirl.create(:quick_mix, user: recording.owner, recording: recording, autowire: false) FactoryGirl.create(:recording_comment, recording: recording, user: recording.owner) - FactoryGirl.create(:recording_like, recording: recording, claimed_recording: recording.claimed_recordings.first, favorite:true) + FactoryGirl.create(:recording_like, recording: recording, claimed_recording: recording.claimed_recordings.first, favorite: true) FactoryGirl.create(:playable_play, playable_id: recording.id, playable_type: 'JamRuby::Recording') FactoryGirl.create(:recorded_video, user: recording.owner, recording: recording) recording.reload @@ -1099,9 +1114,9 @@ describe Recording do describe "add_timeline" do - let!(:recorded_jam_track_track) {FactoryGirl.create(:recorded_jam_track_track)} - let(:recording) {recorded_jam_track_track.recording} - let(:timeline_data) {{"sample" => "data"}} + let!(:recorded_jam_track_track) { FactoryGirl.create(:recorded_jam_track_track) } + let(:recording) { recorded_jam_track_track.recording } + let(:timeline_data) { {"sample" => "data"} } let(:good_timeline) { { "global" => {"recording_start_time" => 0, "jam_track_play_start_time" => 0, "jam_track_recording_start_play_offset" => 0}, "tracks" => [ @@ -1111,7 +1126,7 @@ describe Recording do "type" => "jam_track" } ] - } + } } it "applies timeline data correctly" do diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb index 47f5e73d3..44201b6de 100644 --- a/web/app/controllers/api_recordings_controller.rb +++ b/web/app/controllers/api_recordings_controller.rb @@ -78,6 +78,18 @@ class ApiRecordingsController < ApiController end end + def create_immediately + + @recording = Recording.create_immediately(current_user, params) + + if @recording.errors.any? + response.status = :unprocessable_entity + respond_with @recording + else + respond_with @recording, responder: ApiResponder, :location => api_recordings_detail_url(@recording) + end + end + end def start music_session = ActiveMusicSession.find(params[:music_session_id]) diff --git a/web/config/routes.rb b/web/config/routes.rb index 159f8caa8..af33a910a 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -583,6 +583,8 @@ SampleApp::Application.routes.draw do match '/recordings/:id/timeline' => 'api_recordings#add_timeline', :via => :post, :as => 'api_recordings_timeline' match '/recordings/:id/video_data' => 'api_recordings#add_video_data', :via => :post, :as => 'api_recordings_video_data' match '/recordings/:id/video_data' => 'api_recordings#delete_video_data', :via => :delete, :as => 'api_recordings_video_data_delete' + match '/recordings' => 'api_recordings#create_immediately', :via => :post + # Recordings - recorded_tracks match '/recordings/:id/tracks/:track_id' => 'api_recordings#show_recorded_track', :via => :get, :as => 'api_recordings_show_recorded_track'