diff --git a/web/app/assets/javascripts/react-components/PopupSessionRecording.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupSessionRecording.js.jsx.coffee index 192356b18..270b10107 100644 --- a/web/app/assets/javascripts/react-components/PopupSessionRecording.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/PopupSessionRecording.js.jsx.coffee @@ -3,8 +3,6 @@ logger = context.JK.logger MIX_MODES = context.JK.MIX_MODES mixins = [] -#MixerActions = @MixerActions -#MixerStore = context.MixerStore RecordingActions = @RecordingActions mixins.push(Reflux.listenTo(@SessionMyTracksStore,"onInputChanged")) @@ -15,7 +13,6 @@ mixins.push(Reflux.listenTo(@AppStore,"onAppInit")) mixins: mixins onAppInit: (@app) -> - @setState(app: @app) onInputChanged: (sessionMixers) -> inputGroupMixers = {} @@ -38,35 +35,32 @@ mixins.push(Reflux.listenTo(@AppStore,"onAppInit")) this.setState(isRecording: recordingState.isRecording, recordedOnce: this.state.recordedOnce || recordingState.isRecording) handleSettingSubmit: (settings) -> - volSetting = volume: @state.inputGroupMixers.mixer[0].volume_left - recordSettings = $.extend({}, settings, volSetting) - console.log('__DEBUG__ recordSettings', recordSettings) - - #TODO: pass settings to backend? - success = true - if success - @startStopRecording(recordSettings) + volume = volume: @state.inputGroupMixers.mixer[0].volume_left + recordSettings = $.extend({}, settings, volume) + try + localStorage.setItem("recordSettings", JSON.stringify(recordSettings)) + catch e + logger.info("error while saving recordSettings to localStorage") + logger.log(e.stack) + @startStopRecording(recordSettings) + closeDialog: () -> + @app.layout.cancelDialog('session-recording') + startStopRecording: (settings) -> - console.log('__DEBUG__ @state.isRecording', @state.isRecording) if @state.isRecording RecordingActions.stopRecording() else recordChat = settings.includeChat recordVideo = settings.recordingInputType == 'audio-video' - if recordVideo return unless context.JK.videoIsOngoing logger.debug("@inputType, @udiotye", recordChat, recordVideo) RecordingActions.startRecording(recordVideo, recordChat) - @closeDialog() - - closeDialog: () -> - @state.app.layout.cancelDialog('session-recording'); render: () -> - if @state.app && Object.keys(@state.inputGroupMixers).length > 0 then `
+ if Object.keys(@state.inputGroupMixers).length > 0 then `
diff --git a/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee index 87251c193..1ffe68e02 100644 --- a/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionRecordBtn.js.jsx.coffee @@ -49,8 +49,8 @@ AppStore = context.AppStore buttons: buttons}) render: () -> - ` + ` - {this.state.isRecording ? 'STOP RECORD' : 'RECORD'} + {this.state.isRecording ? 'STOP RECORDING' : 'RECORD'} ` }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SessionRecordingSettings.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionRecordingSettings.js.jsx.coffee index 50cda28d7..be258421b 100644 --- a/web/app/assets/javascripts/react-components/SessionRecordingSettings.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionRecordingSettings.js.jsx.coffee @@ -3,34 +3,33 @@ mixins = [] ExternalVideoStore = context.ExternalVideoStore -#mixins.push(Reflux.listenTo(@AppStore,"onAppInit")) mixins.push(Reflux.listenTo(ExternalVideoStore, "onExternalVideoStateChanged")) @SessionRecordingSettings = React.createClass({ mixins: mixins - # beforeShow: (args) -> - # alert('beforeShow') - - # afterHide: (args) -> - - # onAppInit: (@app) -> - # dialogBindings = { - # 'beforeShow': @beforeShow, - # 'afterHide': @afterHide - # }; - - # @app.bindDialog('session-recording', dialogBindings); - getInitialState: () -> - { - recordingInputType: 'audio-only', - recordingName: '', - audioFormat: 'ogg', - videoFormat: 'mp4', - audioDelay: 0, - includeChat: false, - } + try + settings = localStorage.getItem('recordSettings') + rs = JSON.parse(settings) + { + recordingInputType: rs.recordingInputType, + audioFormat: rs.audioFormat, + videoFormat: rs.videoFormat, + audioDelay: rs.audioDelay, + includeChat: rs.includeChat, + } + catch e + console.error('error loading recording settings from local storage', e.message) + logger.log(e.stack) + { + recordingInputType: 'audio-only', + recordingName: '', + audioFormat: 'ogg', + videoFormat: 'mp4', + audioDelay: 0, + includeChat: false, + } setRecordingNameChange: (e) -> @setState(recordingName: e.target.value) @@ -39,20 +38,32 @@ mixins.push(Reflux.listenTo(ExternalVideoStore, "onExternalVideoStateChanged")) @setState(recordingInputType: e.target.value) setAudioFormatChange: (e) -> - @setState(audioFormat: e.target.value) + $root = $(@getDOMNode()) + audioFormat = $root.find("#audio-format").val() + @setState(audioFormat: audioFormat) setVideoFormatChange: (e) -> - @setState(videoFormat: e.target.value) + $root = $(@getDOMNode()) + videoFormat = $root.find("#video-format").val() + @setState(videoFormat: videoFormat) setAudioDelay: (e) -> @setState(audioDelay: e.target.value) + setIncludeChat: (e) -> + @setState(includeChat: e.target.checked) + cancel: (e) -> e.preventDefault() @props.handleCancel() - startRecording: () -> - @props.handleSubmit(@state) + startRecording: (e) -> + e.preventDefault() + $root = $(@getDOMNode()) + $recName = $root.find('#recording-name') + if $recName.val().length > 0 + @props.handleSubmit(@state) + @setState(recordingName: '') onExternalVideoStateChanged: (videoState) -> $root = $(this.getDOMNode()) @@ -76,26 +87,26 @@ mixins.push(Reflux.listenTo(ExternalVideoStore, "onExternalVideoStateChanged"))
-
+
-
+
- +
- +
- {audioFormatOptions}
@@ -103,7 +114,7 @@ mixins.push(Reflux.listenTo(ExternalVideoStore, "onExternalVideoStateChanged"))
- {videoFormatOptions}
@@ -114,9 +125,9 @@ mixins.push(Reflux.listenTo(ExternalVideoStore, "onExternalVideoStateChanged"))
-
+
- +
@@ -130,18 +141,31 @@ mixins.push(Reflux.listenTo(ExternalVideoStore, "onExternalVideoStateChanged")) context.JK.dropdown($root.find('select')) - $includeChatCheckbox = $root.find('#include-chat') - context.JK.checkbox($includeChatCheckbox) - $inputAudioRadioBtn = $root.find('#recording-input-audio') - context.JK.checkbox($inputAudioRadioBtn) + $inputBothRadioBtn = $root.find('#recording-input-both') - $inputBothRadioBtn = $root.find('#recording-input-both') + #only enable audio & video radio button if a video is ongoing $inputBothRadioBtn.iCheck().attr('disabled', !context.JK.videoIsOngoing) - context.JK.checkbox($inputBothRadioBtn) + # if @state.recordingInputType == 'audio-video' + # $inputBothRadioBtn.iCheck('check').attr('checked', true) + # else + # $inputAudioRadioBtn.iCheck('check').attr('checked', true) + + $inputBothRadioBtn.prop('disabled',true).iCheck('update').iCheck('uncheck'); + + context.JK.checkbox($inputAudioRadioBtn) + context.JK.checkbox($inputBothRadioBtn) + + $includeChatCheckbox = $root.find('#include-chat') + $includeChatCheckbox.iCheck('check').attr('checked', @state.includeChat) + $includeChatCheckbox.on 'ifToggled', (e) => + @setState(includeChat: e.target.checked) + context.JK.checkbox($includeChatCheckbox) componentDidUpdate: () -> $root = $(this.getDOMNode()) context.JK.dropdown($root.find('select')) + $root.find('#audio-format').unbind('change').change(this.setAudioFormatChange) + $root.find('#video-format').unbind('change').change(this.setVideoFormatChange) }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SessionRecordingVu.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionRecordingVu.js.jsx.coffee index 9d8fe5dc4..58adb1536 100644 --- a/web/app/assets/javascripts/react-components/SessionRecordingVu.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionRecordingVu.js.jsx.coffee @@ -4,7 +4,7 @@ mixins = [] MixerStore = context.MixerStore -mixins.push(Reflux.listenTo(MixerStore,"onInputChanged")) +mixins.push(Reflux.listenTo(SessionMyTracksStore,"onInputChanged")) @SessionRecordingVu = React.createClass({ mixins: mixins @@ -17,16 +17,15 @@ mixins.push(Reflux.listenTo(MixerStore,"onInputChanged")) categoryMixers = mixers.simulatedMusicCategoryMixers[MIX_MODES.PERSONAL] if categoryMixers inputGroupMixers = categoryMixers - @setState({mixers: inputGroupMixers}) getInitialState: () -> { mixers: @props.mixers } - render: () -> - monitorMuteMixer = @props.mixers.muteMixer[0] + renderVu: () -> + monitorMuteMixer = @state.mixers.muteMixer[0] monitorMuteMixerId = mixers?.id - monitorVolumeLeft = @props.mixers.mixer[0].volume_left + monitorVolumeLeft = @state.mixers.mixer[0].volume_left monitorMuteClasses = classNames({ 'track-icon-mute': true 'enabled' : !monitorMuteMixer?.mute @@ -38,7 +37,6 @@ mixins.push(Reflux.listenTo(MixerStore,"onInputChanged")) Volume:
-
@@ -63,17 +61,19 @@ mixins.push(Reflux.listenTo(MixerStore,"onInputChanged"))

To change the volume of individual tracks in the mix, use the personal mix controls in the session window.

-
- ` +
` + + render: () -> + if Object.keys(@state.mixers).length > 0 then @renderVu() else `Loading...` handleAudioInputMute: (e) -> e.preventDefault() muting = $(e.currentTarget).is('.enabled') - MixerActions.mute(@props.mixers.muteMixer, muting) + MixerActions.mute(@state.mixers.muteMixer, muting) handleAudioInputMuteCheckbox: (e) -> muting = $(e.target).is(':checked') - MixerActions.mute(@props.mixers.muteMixer, muting) + MixerActions.mute(@state.mixers.muteMixer, muting) componentDidMount: () -> $root = jQuery(this.getDOMNode()) @@ -83,7 +83,7 @@ mixins.push(Reflux.listenTo(MixerStore,"onInputChanged")) context.JK.checkbox($audioInputMuteCheckbox) $audioInputMuteCheckbox.on('ifChanged', @handleAudioInputMuteCheckbox) - if @props.mixers.muteMixer[0].mute + if @state.mixers.muteMixer[0].mute $audioInputMuteCheckbox.iCheck('check').attr('checked', true) else $audioInputMuteCheckbox.iCheck('uncheck').attr('checked', false) diff --git a/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee b/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee index d4a277618..906e9758c 100644 --- a/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/stores/RecordingStore.js.jsx.coffee @@ -14,15 +14,17 @@ BackendToFrontendFPS = { @RecordingStore = Reflux.createStore( { listenables: @RecordingActions - recordingWindow: null + #recordingWindow: null + recordingWindowOpened: false init: -> # Register with the app store to get @app this.listenTo(context.AppStore, this.onAppInit) onSessionEnded: () -> - if @recordingWindow? - @recordingWindow.close() + if @recordingWindowOpened + #@recordingWindow.close() + @closeRecordingWindow() onAppInit: (app) -> @app = app @@ -46,7 +48,6 @@ BackendToFrontendFPS = { # @recordingModel.startRecording(recordVideo, recordChat, frameRate) onStartRecording: `async function(recordVideo, recordChat) { - console.log("onStartRecording...", recordVideo, recordChat) let frameRate = 0; if (recordVideo) { //if (await context.jamClient.GetCurrentVideoFrameRate() != null) { @@ -58,7 +59,6 @@ BackendToFrontendFPS = { const WebCamRecordActive = 1; const ScreenRecordActive = 2; logger.debug('onStartRecording: recordVideo: '+recordVideo+' , recordChat:' +recordChat+' frameRate: '+frameRate); - //logger.debug('this.recordingModel', this.recordingModel) this.recordingModel.startRecording(recordVideo, recordChat, frameRate); }` @@ -69,15 +69,15 @@ BackendToFrontendFPS = { details.cause = 'starting' @mixTransferred = false this.trigger(details) - - @popupRecordingControls() unless @recordingWindow? + @popupRecordingControls() unless @recordingWindowOpened onStartedRecording: (details) -> details.cause = 'started' @mixTransferred = false this.trigger(details) - @popupRecordingControls() unless @recordingWindow? + #@popupRecordingControls() unless @recordingWindowOpened + @closeRecordingWindow() if @recordingWindowOpened onStoppingRecording: (details) -> details.cause = 'stopping' @@ -86,31 +86,33 @@ BackendToFrontendFPS = { onStoppedRecording: (details) -> details.cause = 'stopped' - - if @recordingWindow? - @recordingWindow.close() + if @recordingWindowOpened + #@recordingWindow.close() + @closeRecordingWindow() this.trigger(details) onAbortedRecording: (details) -> details.cause = 'aborted' - if @recordingWindow? - @recordingWindow.close() + if @recordingWindowOpened + #@recordingWindow.close() + @closeRecordingWindow() this.trigger(details) onOpenRecordingControls: () -> logger.debug("recording controls opening") - - if @recordingWindow? - @recordingWindow.close() + if @recordingWindowOpened + #@recordingWindow.close() + @closeRecordingWindow() @popupRecordingControls() onRecordingControlsClosed: () -> logger.debug("recording controls closed") - @recordingWindow = null + #@recordingWindow = null + @recordingWindowOpened = false onMixTransferred: () -> @mixTransferred = true @@ -121,6 +123,10 @@ BackendToFrontendFPS = { #@recordingWindow.ParentRecordingStore = context.RecordingStore #@recordingWindow.ParentIsRecording = @recordingModel.isRecording() @app.layout.showDialog('session-recording', {}) + @recordingWindowOpened = true + closeRecordingWindow: () -> + @app.layout.cancelDialog('session-recording'); + @recordingWindowOpened = false } ) diff --git a/web/spec/features/session_recording_spec.rb b/web/spec/features/session_recording_spec.rb new file mode 100644 index 000000000..e6a9fa912 --- /dev/null +++ b/web/spec/features/session_recording_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe "Session Recording", :js => true, :type => :feature, :capybara_feature => true do + subject { page } + + before(:all) do + #Capybara.javascript_driver = :poltergeist + #Capybara.current_driver = Capybara.javascript_driver + #Capybara.default_max_wait_time = 30 # these tests are SLOOOOOW + end + + let(:creator) { FactoryGirl.create(:user) } + + before(:each) do + ActiveMusicSession.delete_all + end + + it "video checkbox is diabled if video session is not started" do + creator.subscription_plan_code = SubscriptionDefinitions::JAM_PLATINUM + creator.subscription_trial_ends_at = 1.day.ago + creator.admin_override_plan_code = SubscriptionDefinitions::JAM_PLATINUM + creator.admin_override_ends_at = 1.day.from_now + creator.save! + + create_new_session(creator: creator, include_video: true) do + expect(page.find("input#recording-input-both")).not_to be_checked + # page.find('.btnCancel').click + # video_window = page.window_opened_by do + # page.find('.in-session-controls a.session-video-btn').click + # end + # sleep 1 + # within_window(video_window) do + # click_link "JOIN ROOM" + # end + # sleep 1 + end + end + + it "start/stop recording", focus: true do + create_new_session(creator: creator) do + page.find('.recording-left .form-actions a', text: 'START RECORDING').click + page.find('.in-session-controls a.session-record-btn', text: 'STOP RECORDING').click + page.find('.in-session-controls a.session-record-btn', text: 'RECORD') + end + end +end \ No newline at end of file diff --git a/web/spec/support/utilities.rb b/web/spec/support/utilities.rb index b48177a59..ca17b81f8 100644 --- a/web/spec/support/utilities.rb +++ b/web/spec/support/utilities.rb @@ -344,8 +344,8 @@ end # will select the value from a easydropdown'ed select element def jk_select(text, select) # the approach here is to find the hidden select element, and work way back up to the elements that need to be interacted with - find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown") and not(contains(@class, "disabled"))]').trigger(:click) - find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown-wrapper") and contains(@class, "easydropdown-wrapper") and contains(@class, "open") ]').find('li', text: text).trigger(:click) + find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown easydropdown") and not(contains(@class, "disabled"))]').click + find(select, :visible => false).find(:xpath, 'ancestor::div[contains(@class, "dropdown-wrapper") and contains(@class, "easydropdown-wrapper") and contains(@class, "open") ]').find('li', text: text).click # works, but is 'cheating' because of visible = false #select(genre, :from => 'genres', :visible => false) @@ -418,6 +418,78 @@ def create_session(options={}) end +#===START test helpers for session creation and recording windows=== +def create_new_session(options={}, &block) + creator = options[:creator] || FactoryGirl.create(:user) + unique_session_name = options[:name] || "create_join_session #{SecureRandom.urlsafe_base64}" + audio_format = options[:audio_format] || '.mp3' + video_format = options[:video_format] || '.mp4' + audio_delay = options[:audio_delay] || 0 + include_voice_chat = options[:include_voice_chat] || false + include_video = options[:include_video] || false + + in_client(creator) do + #page.driver.resize(1500, 800) # makes sure all the elements are visible + emulate_client + fast_signin(creator, "/client#/createSession") + expect(page).to have_selector('h1', text: 'sessions') + + find('a.quick-start-solo').click() + find('.in-session-controls a.session-record-btn').click() + + within('#session-recording') do + expect(page).to have_selector('h1', text: 'recording') + expect(page).to have_selector('a', text: 'START RECORDING') + + if include_video + find('.recording-type-both ins.iCheck-helper').click + end + fill_in('Name', :with => unique_session_name) + jk_select(audio_format, 'select#audio-format') + jk_select(video_format, 'select#video-format') + fill_in('Audio Delay (ms)', :with => audio_delay) + if include_voice_chat + find('.include-chat-check ins.iCheck-helper').click + end + end + yield + end + return creator, unique_session_name +end + +def create_session_and_join(creator, joiners=[], options={}) + options[:creator] = creator + creator, unique_session_name = create_new_session(options) + + # find session in second client + [*joiners].each do |joiner| + join_session(joiner, name: unique_session_name) + end + + return creator, unique_session_name +end + +def join_with_session(joiner, options) + name = options[:name] + + in_client(joiner) do + #page.driver.resize(1500, 800) # makes sure all the elements are visible + emulate_client + fast_signin(joiner, "/client#/findSession") + + # verify the session name is seen by second client + expect(page).to have_text(name) + find('.join-link').click + unless options[:no_verify] + find('#btn-accept-terms').click + expect(page).to have_selector('h2', text: 'my live tracks') + find('#session-screen .session-my-tracks .session-track.my-track') + end + end + +end +#===END test helpers for session creation and recording windows=== + def schedule_session(options = {}) creator = options[:creator] || FactoryGirl.create(:user) unique_session_name = options[:name] || "schedule_session #{SecureRandom.urlsafe_base64}"