diff --git a/db/manifest b/db/manifest index 5c93932dc..94acb060f 100755 --- a/db/manifest +++ b/db/manifest @@ -278,4 +278,4 @@ show_whats_next_count.sql recurly_adjustments.sql signup_hints.sql packaging_notices.sql -first_played_jamtrack_at.sql \ No newline at end of file +first_played_jamtrack_at.sql diff --git a/web/app/assets/images/content/icon_cam.png b/web/app/assets/images/content/icon_cam.png new file mode 100644 index 000000000..e32bede67 Binary files /dev/null and b/web/app/assets/images/content/icon_cam.png differ diff --git a/web/app/assets/javascripts/accounts.js b/web/app/assets/javascripts/accounts.js index b7af8f033..a3b75f9b9 100644 --- a/web/app/assets/javascripts/accounts.js +++ b/web/app/assets/javascripts/accounts.js @@ -8,8 +8,10 @@ var rest = context.JK.Rest(); var userId; var user = {}; + function beforeShow(data) { + console.log("beforeShow", data) userId = data.id; } @@ -43,6 +45,11 @@ var invalidProfiles = prettyPrintAudioProfiles(context.JK.getBadConfigMap()); var sessionSummary = summarizeSession(userDetail); + var webcam = context.jamClient.FTUECurrentSelectedVideoDevice + if (webcam==null || typeof(webcam)=="undefined" || webcam.length==0) { + webcam = "None Configured" + } + var $template = $(context._.template($('#template-account-main').html(), { email: userDetail.email, name: userDetail.name, @@ -56,6 +63,7 @@ invalidProfiles : invalidProfiles, isNativeClient: gon.isNativeClient, musician: context.JK.currentUserMusician, + webcamName: webcam, sales_count: userDetail.sales_count } , { variable: 'data' })); @@ -65,8 +73,9 @@ $('#account-scheduled-sessions-link').show(); } else { $('#account-scheduled-sessions-link').hide(); - } - } + } + + }// function function prettyPrintAudioProfiles(profileMap) { var profiles = ""; @@ -110,6 +119,7 @@ $('#account-content-scroller').on('click', '#account-edit-subscriptions-link', function(evt) { evt.stopPropagation(); navToEditSubscriptions(); return false; } ); $('#account-content-scroller').on('click', '#account-edit-payments-link', function(evt) { evt.stopPropagation(); navToEditPayments(); return false; } ); $('#account-content-scroller').on('click', '#account-edit-audio-link', function(evt) { evt.stopPropagation(); navToEditAudio(); return false; } ); + $('#account-content-scroller').on('click', '#account-edit-video-link', function(evt) { evt.stopPropagation(); navToEditVideo(); return false; } ); $('#account-content-scroller').on('avatar_changed', '#profile-avatar', function(evt, newAvatarUrl) { evt.stopPropagation(); updateAvatar(newAvatarUrl); return false; }) // License dialog: @@ -158,6 +168,11 @@ window.location = "/client#/account/audio" } + function navToEditVideo() { + resetForm() + window.location = "/client#/account/video" + } + function navToPaymentHistory() { window.location = '/client#/account/paymentHistory' } @@ -178,6 +193,7 @@ } function initialize() { + var screenBindings = { 'beforeShow': beforeShow, 'afterShow': afterShow diff --git a/web/app/assets/javascripts/accounts_video_profile.js b/web/app/assets/javascripts/accounts_video_profile.js new file mode 100644 index 000000000..fbb1a6d53 --- /dev/null +++ b/web/app/assets/javascripts/accounts_video_profile.js @@ -0,0 +1,32 @@ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.AccountVideoProfile = function (app) { + var $webcamViewer = new context.JK.WebcamViewer() + function initialize() { + var screenBindings = { + 'beforeShow': beforeShow, + 'beforeHide':beforeHide + }; + app.bindScreen('account/video', screenBindings); + + $webcamViewer.init($(".webcam-container")) + } + + function beforeShow() { + $webcamViewer.beforeShow() + } + + function beforeHide() { + $webcamViewer.setVideoOff() + } + + this.beforeShow = beforeShow + this.beforeHide = beforeHide + this.initialize = initialize + return this; + }; + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js index f78022877..1bb456b43 100644 --- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js +++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js @@ -10,10 +10,10 @@ var $dialog = null; function resetForm() { - // remove all display errors $('#recording-finished-dialog form .error-text').remove() $('#recording-finished-dialog form .error').removeClass("error") + removeGoogleLoginErrors() } function beforeShow() { @@ -130,11 +130,47 @@ return false; } + function startGoogleLogin(e) { + e.preventDefault() + logger.debug("Starting google login") + window._oauth_win = window.open("/auth/google_login", "Log In to Google", "height=500,width=500,menubar=no,resizable=no,status=no"); + + window._oauth_callback = function() { + window._oauth_win.close() + setGoogleAuthState() + } + return false; + } + function claimRecording(e) { - resetForm(); - registerClaimRecordingHandlers(false); + registerClaimRecordingHandlers(false) + var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked') + + if (upload_to_youtube) { + $.ajax({ + type: "GET", + dataType: "json", + url: "/auth/has_google_auth" + }).success(function(data) { + if(data.has_google_auth) { + performClaim() + } else { + var error_ul = $('') + $('#recording-finished-dialog form [purpose=upload_to_youtube]').addClass('error').append(error_ul) + } + }).always(function () { + registerClaimRecordingHandlers(true); + }) + } else { + performClaim() + } + + return false; + } + + function performClaim() { var name = $('#recording-finished-dialog form input[name=name]').val(); var description = $('#recording-finished-dialog form textarea[name=description]').val(); var genre = $('#recording-finished-dialog form select[name=genre]').val(); @@ -151,59 +187,53 @@ save_video: save_video, upload_to_youtube: upload_to_youtube }) - .done(function () { - $dialog.data('result', {keep:true}); - app.layout.closeDialog('recordingFinished'); - context.JK.GA.trackMakeRecording(); - }) - .fail(function (jqXHR) { - if (jqXHR.status == 422) { - var errors = JSON.parse(jqXHR.responseText); + .done(function () { + $dialog.data('result', {keep:true}); + app.layout.closeDialog('recordingFinished'); + context.JK.GA.trackMakeRecording(); + }) + .fail(function (jqXHR) { + if (jqXHR.status == 422) { + var errors = JSON.parse(jqXHR.responseText); - var $name_errors = context.JK.format_errors('name', errors); - if ($name_errors) $('#recording-finished-dialog form input[name=name]').closest('div.field').addClass('error').end().after($name_errors); + var $name_errors = context.JK.format_errors('name', errors); + if ($name_errors) $('#recording-finished-dialog form input[name=name]').closest('div.field').addClass('error').end().after($name_errors); - var $description_errors = context.JK.format_errors('description', errors); - if ($description_errors) $('#recording-finished-dialog form input[name=description]').closest('div.field').addClass('error').end().after($description_errors); + var $description_errors = context.JK.format_errors('description', errors); + if ($description_errors) $('#recording-finished-dialog form input[name=description]').closest('div.field').addClass('error').end().after($description_errors); - var $genre_errors = context.JK.format_errors('genre', errors); - if ($genre_errors) $('#recording-finished-dialog form select[name=genre]').closest('div.field').addClass('error').end().after($genre_errors); + var $genre_errors = context.JK.format_errors('genre', errors); + if ($genre_errors) $('#recording-finished-dialog form select[name=genre]').closest('div.field').addClass('error').end().after($genre_errors); - var $is_public_errors = context.JK.format_errors('is_public', errors); - if ($is_public_errors) $('#recording-finished-dialog form input[name=is_public]').closest('div.field').addClass('error').end().after($is_public_errors); + var $is_public_errors = context.JK.format_errors('is_public', errors); + if ($is_public_errors) $('#recording-finished-dialog form input[name=is_public]').closest('div.field').addClass('error').end().after($is_public_errors); - var $save_video_errors = context.JK.format_errors('save_video', errors); - if ($save_video_errors) $('#recording-finished-dialog form input[name=save_video]').closest('div.field').addClass('error').end().after($save_video_errors); + var $save_video_errors = context.JK.format_errors('save_video', errors); + if ($save_video_errors) $('#recording-finished-dialog form input[name=save_video]').closest('div.field').addClass('error').end().after($save_video_errors); + + var recording_error = context.JK.get_first_error('recording_id', errors); - var $upload_to_youtube_errors = context.JK.format_errors('upload_to_youtube', errors); - if ($upload_to_youtube_errors) $('#recording-finished-dialog form input[name=upload_to_youtube]').closest('div.field').addClass('error').end().after($upload_to_youtube_errors); + if (recording_error) context.JK.showErrorDialog(app, "Unable to claim recording.", recording_error); + } + else { + logger.error("unable to claim recording %o", arguments); - var recording_error = context.JK.get_first_error('recording_id', errors); - - if (recording_error) context.JK.showErrorDialog(app, "Unable to claim recording.", recording_error); - } - else { - logger.error("unable to claim recording %o", arguments); - - context.JK.showErrorDialog(app, "Unable to claim recording.", jqXHR.responseText); - } - }) - .always(function () { - registerClaimRecordingHandlers(true); - }); - return false; + context.JK.showErrorDialog(app, "Unable to claim recording.", jqXHR.responseText); + } + }) + .always(function () { + registerClaimRecordingHandlers(true); + }); } function registerClaimRecordingHandlers(onOff) { + $('#keep-session-recording').off('click', claimRecording) + $('#recording-finished-dialog form').off('submit', claimRecording); + if (onOff) { $('#keep-session-recording').on('click', claimRecording); $('#recording-finished-dialog form').on('submit', claimRecording); } - else { - $('#keep-session-recording').off('click', claimRecording) - $('#recording-finished-dialog form').off('submit', claimRecording); - } - } function registerDiscardRecordingHandlers(onOff) { @@ -237,6 +267,35 @@ .on('pause', onPause) .on('play', onPlay) .on('change-position', onChangePlayPosition); + $dialog.find(".google_login_button").on('click', startGoogleLogin); + + // Check for google authorization using AJAX and show/hide the + // google login button / "signed in" label as appropriate: + $(window).on('focus', function() { + setGoogleAuthState(); + }); + } + + function setGoogleAuthState() { + $.ajax({ + type: "GET", + dataType: "json", + url: "/auth/has_google_auth" + }).success(function(data) { + if(data.has_google_auth) { + $("input.google_login_button").addClass("hidden") + $("span.signed_in_to_google").removeClass("hidden") + removeGoogleLoginErrors() + } else { + $("span.signed_in_to_google").addClass("hidden") + $("input.google_login_button").removeClass("hidden") + } + }) + } + + function removeGoogleLoginErrors() { + $("ul.error-text.upload_to_youtube").remove() + $('#recording-finished-dialog form div[purpose=upload_to_youtube]').removeClass('error') } function setRecording(recordingData) { @@ -267,7 +326,6 @@ playbackControls = new context.JK.PlaybackControls($('#recording-finished-dialog .recording-controls')); registerStaticEvents(); - initializeButtons(); }; diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index f9125e78d..1157bc981 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -21,6 +21,7 @@ var frameSize = 2.5; var fakeJamClientRecordings = null; var p2pCallbacks = null; + var videoShared = false; var metronomeActive=false; var metronomeBPM=false; var metronomeSound=false; @@ -59,6 +60,41 @@ function FTUESetMusicProfileName() { } + + function FTUESelectVideoCaptureDevice(device, settings) { + + } + function FTUESetVideoEncodeResolution(resolution) { + + } + function FTUEGetVideoCaptureDeviceNames() { + return ["Built-in Webcam HD"] + } + function FTUECurrentSelectedVideoDevice() { + return "Built-in Webcam HD" + } + function FTUEGetAvailableEncodeVideoResolutions() { + return { + 1: "1024x768", + 2: "800x600" + } + } + + function isSessVideoShared() { + return videoShared; + } + + function SessStopVideoSharing() { + videoShared=false; + } + + function SessStartVideoSharing(bitrate) { + if (!bitrate || typeof(bitrate)=="undefined") { + bitrate = 0 + } + videoShared=true; + } + function FTUEGetInputLatency() { dbg("FTUEGetInputLatency"); return 2; @@ -1151,6 +1187,17 @@ this.ClosePreviewRecording = ClosePreviewRecording; this.OnDownloadAvailable = OnDownloadAvailable; + // Video functionality: + this.FTUESelectVideoCaptureDevice = FTUESelectVideoCaptureDevice + this.FTUESetVideoEncodeResolution = FTUESetVideoEncodeResolution; + this.FTUEGetVideoCaptureDeviceNames = FTUEGetVideoCaptureDeviceNames; + this.FTUECurrentSelectedVideoDevice = FTUECurrentSelectedVideoDevice; + this.FTUEGetAvailableEncodeVideoResolutions = FTUEGetAvailableEncodeVideoResolutions; + + this.isSessVideoShared = isSessVideoShared; + this.SessStopVideoSharing = SessStopVideoSharing; + this.SessStartVideoSharing = SessStartVideoSharing; + // Clipboard this.SaveToClipboard = SaveToClipboard; diff --git a/web/app/assets/javascripts/scheduled_session.js.erb b/web/app/assets/javascripts/scheduled_session.js.erb index 791b6b5c9..3cdc9f84b 100644 --- a/web/app/assets/javascripts/scheduled_session.js.erb +++ b/web/app/assets/javascripts/scheduled_session.js.erb @@ -937,6 +937,12 @@ } } + if(optionRequiresMultiplayerProfile()) { + if(context.JK.guardAgainstSinglePlayerProfile(app).canPlay == false) { + return false; + } + } + var valid = beforeMoveStep(); if (!valid) { return false; diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index be5fc2738..31c2a60f6 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -13,6 +13,7 @@ var modUtils = context.JK.ModUtils; var logger = context.JK.logger; var self = this; + var webcamViewer = new context.JK.WebcamViewer() var defaultParticipant = { tracks: [{ @@ -143,6 +144,7 @@ var shareDialog = new JK.ShareDialog(context.JK.app, sessionId, "session"); shareDialog.initialize(context.JK.FacebookHelperInstance); + webcamViewer.beforeShow() } function beforeDisconnect() { @@ -210,7 +212,6 @@ var singlePlayerCheckOK = true; // to know whether we are allowed to be in this session, we have to check if we are the creator when checking against single player functionality if(musicSession.user_id != context.JK.currentUserId) { - var canPlay = context.JK.guardAgainstSinglePlayerProfile(app, function () { promptLeave = false; }); @@ -503,6 +504,7 @@ } function beforeHide(data) { + webcamViewer.setVideoOff() $fluidTracks.removeClass('showing'); if(screenActive) { // this path is possible if FTUE is invoked on session page, and they cancel @@ -714,7 +716,7 @@ function _initDialogs() { configureTrackDialog.initialize(); - addNewGearDialog.initialize(); + addNewGearDialog.initialize(); } // Get the latest list of underlying audio mixer channels, and populates: @@ -2011,15 +2013,12 @@ var liveTrackWidthPct = Math.ceil(100 * liveTrackWidth/totalWidth); //logger.debug("resizeFluid: ", minimumLiveTrackWidth, otherAudioWidth, otherAudioWidthPct, liveTrackWidthPct, liveTrackWidthPct) - $audioTracks.css('width', otherAudioWidthPct + '%'); $liveTracks.css('width', liveTrackWidthPct + '%'); } function _addRecordingTrack(trackData, mixer, oppositeMixer) { - otherAudioFilled(); - $('.session-recordings .recording-controls').show(); var parentSelector = '#session-recordedtracks-container'; @@ -2479,6 +2478,18 @@ return false; } + function sessionWebCam(e) { + e.preventDefault(); + if(webcamViewer.isVideoShared()) { + $('#session-webcam').removeClass("selected") + } else { + $('#session-webcam').addClass("selected") + } + + webcamViewer.toggleWebcam() + return false; + } + // http://stackoverflow.com/questions/2604450/how-to-create-a-jquery-clock-timer function updateRecordingTimer() { @@ -2739,7 +2750,6 @@ }// function function openBackingTrackFile(e) { - // just ignore the click if they are currently recording for now if(sessionModel.recordingModel.isRecording()) { app.notify({ @@ -3075,7 +3085,6 @@ var mode = data.playbackMode; // will be either 'self' or 'cricket' logger.debug("setting metronome playback mode: ", mode) - var isCricket = mode == 'cricket'; context.jamClient.setMetronomeCricketTestState(isCricket); } @@ -3108,6 +3117,7 @@ function events() { $('#session-leave').on('click', sessionLeave); $('#session-resync').on('click', sessionResync); + $('#session-webcam').on('click', sessionWebCam); $('#session-contents').on("click", '[action="delete"]', deleteSession); $tracksHolder.on('click', 'div[control="mute"]', toggleMute); $('#recording-start-stop').on('click', startStopRecording); @@ -3191,7 +3201,8 @@ $fluidTracks = $screen.find('.session-fluidtracks'); $voiceChat = $screen.find('#voice-chat'); $tracksHolder = $screen.find('#tracks') - + webcamViewer.init($(".webcam-container")) + webcamViewer.setVideoOff() events(); diff --git a/web/app/assets/javascripts/sessionModel.js b/web/app/assets/javascripts/sessionModel.js index 8eeebaddf..ceb4b70b6 100644 --- a/web/app/assets/javascripts/sessionModel.js +++ b/web/app/assets/javascripts/sessionModel.js @@ -215,7 +215,7 @@ // see if we already have tracks; if so, we need to run with these var inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient); - console.log("isNoInputProfile", gearUtils.isNoInputProfile()) + logger.debug("isNoInputProfile", gearUtils.isNoInputProfile()) if(inputTracks.length > 0 || gearUtils.isNoInputProfile() ) { logger.debug("on page enter, tracks are already available") sessionPageEnterDeferred.resolve(inputTracks); diff --git a/web/app/assets/javascripts/webcam_viewer.js.coffee b/web/app/assets/javascripts/webcam_viewer.js.coffee new file mode 100644 index 000000000..6986d35ba --- /dev/null +++ b/web/app/assets/javascripts/webcam_viewer.js.coffee @@ -0,0 +1,104 @@ +$ = jQuery +context = window +context.JK ||= {}; + +context.JK.WebcamViewer = class WebcamViewer + constructor: (@root) -> + @client = context.jamClient + @logger = context.JK.logger + @initialScan = false + @toggleBtn = null + @webcamSelect = null + @resolutionSelect = null + @videoShared=false + @resolution=null + + init: (root) => + @logger.debug 'root', root + @root = root + @toggleBtn = @root.find(".webcam-test-btn") + @webcamSelect = @root.find(".webcam-select-container select") + @resolutionSelect = @root.find(".webcam-resolution-select-container select") + @webcamSelect.on("change", this.selectWebcam) + @logger.debug 'webcamSelect', @webcamSelect + + beforeShow:() => + this.loadWebCams() + this.selectWebcam() + this.loadResolutions() + this.selectResolution() + @initialScan = true + #client.SessSetInsetPosition(5) + #client.SessSetInsetSize(1) + #client.FTUESetAutoSelectVideoLayout(false) + #client.SessSelectVideoDisplayLayoutGroup(1) + + + selectWebcam:(e, data) => + @logger.debug 'Selecting control: ', @webcamSelect + device = @webcamSelect.val() + if device != null and device != '' + @logger.debug 'Selecting webcam: ', device + + selectResolution:() => + @logger.debug 'Selecting res control: ', @resolutionSelect + @resolution = @resolutionSelect.val() + if @resolution != null and @resolution != '' + @logger.debug 'Selecting res: ', @resolution + @client.FTUESetVideoEncodeResolution @resolution + + setVideoOff:() => + if this.isVideoShared() + @client.SessStopVideoSharing() + + isVideoShared:() => + @videoShared + + setToggleState:() => + available = @webcamSelect.find('option').size() > 0 + shared = this.isVideoShared() + @logger.debug 'Setting toggle from : ', shared + @toggleBtn.prop 'disabled', true + @toggleBtn.prop 'disabled', !available + + toggleWebcam:() => + @logger.debug 'Toggling webcam from: ', this.isVideoShared() + if this.isVideoShared() + @client.SessStopVideoSharing() + @videoShared = false + else + @client.SessStartVideoSharing 0 + @videoShared = true + + loadWebCams:() => + devices = @client.FTUEGetVideoCaptureDeviceNames() + selectedDevice = @client.FTUECurrentSelectedVideoDevice() + selectControl = @webcamSelect + context._.each devices, (device) -> + selected = device == selectedDevice + option = $('