From 276442a9b48c7b59553fad4398da8ded02dce9cf Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Wed, 7 Oct 2015 21:12:26 -0500
Subject: [PATCH 1/9] * VRFS-3512 - upload videos to youtube
---
admin/config/application.rb | 1 +
admin/config/environments/development.rb | 1 +
db/up/video_recording.sql | 4 +-
ruby/lib/jam_ruby/app/mailers/admin_mailer.rb | 10 +-
ruby/lib/jam_ruby/models/recording.rb | 4 +
.../lib/jam_ruby/models/user_authorization.rb | 27 ++-
ruby/spec/support/utilities.rb | 4 +
web/Gemfile | 10 +-
.../dialog/deleteVideoConfirmDialog.js | 69 ++++++
.../javascripts/dialog/editRecordingDialog.js | 2 +-
.../dialog/recordingFinishedDialog.js | 33 ++-
web/app/assets/javascripts/feedHelper.js | 40 ++++
.../assets/javascripts/feed_item_recording.js | 1 +
web/app/assets/javascripts/jam_rest.js | 46 ++++
.../assets/javascripts/react-components.js | 1 +
.../PopupConfigureVideoGear.js.jsx.coffee | 2 +-
.../PopupVideoUploader.js.jsx.coffee | 209 +++++++++++++++++
.../actions/VideoUploaderActions.js.coffee | 12 +
.../stores/RecordingStore.js.jsx.coffee | 9 +
.../stores/VideoUploaderStore.js.coffee | 211 ++++++++++++++++++
.../assets/javascripts/sync_viewer.js.coffee | 12 +
.../dialogs/deleteVideoConfirmDialog.css.scss | 13 ++
.../minimal/video_uploader.css.scss | 58 +++++
.../stylesheets/users/syncViewer.css.scss | 2 +-
.../stylesheets/web/recordings.css.scss | 40 ++++
.../controllers/api_recordings_controller.rb | 28 ++-
web/app/controllers/api_users_controller.rb | 15 +-
web/app/controllers/popups_controller.rb | 6 +
web/app/controllers/sessions_controller.rb | 21 +-
.../views/api_claimed_recordings/show.rabl | 2 +-
web/app/views/api_feeds/show.rabl | 2 +-
web/app/views/api_recordings/show.rabl | 4 +-
web/app/views/api_users/authorizations.rabl | 6 +
web/app/views/api_users/google_auth.rabl | 16 ++
.../clients/_sync_viewer_templates.html.slim | 2 +
web/app/views/clients/index.html.erb | 3 +
.../_deleteVideoConfirmDialog.html.slim | 15 ++
web/app/views/dialogs/_dialogs.html.haml | 1 +
.../_recordingFinishedDialog.html.haml | 6 +-
web/app/views/popups/video_upload.html.slim | 3 +
web/app/views/sessions/oauth_complete.erb | 2 +
.../users/_feed_recording_ajax.html.haml | 3 +
web/config/application.rb | 1 +
web/config/environments/development.rb | 1 +
web/config/initializers/omniauth.rb | 4 +-
web/config/routes.rb | 7 +-
.../lib/jam_websockets/router.rb | 4 +-
47 files changed, 938 insertions(+), 35 deletions(-)
create mode 100644 web/app/assets/javascripts/dialog/deleteVideoConfirmDialog.js
create mode 100644 web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
create mode 100644 web/app/assets/javascripts/react-components/actions/VideoUploaderActions.js.coffee
create mode 100644 web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
create mode 100644 web/app/assets/stylesheets/dialogs/deleteVideoConfirmDialog.css.scss
create mode 100644 web/app/assets/stylesheets/minimal/video_uploader.css.scss
create mode 100644 web/app/views/api_users/authorizations.rabl
create mode 100644 web/app/views/api_users/google_auth.rabl
create mode 100644 web/app/views/dialogs/_deleteVideoConfirmDialog.html.slim
create mode 100644 web/app/views/popups/video_upload.html.slim
diff --git a/admin/config/application.rb b/admin/config/application.rb
index 0e13be779..eb5c1c612 100644
--- a/admin/config/application.rb
+++ b/admin/config/application.rb
@@ -110,6 +110,7 @@ module JamAdmin
config.redis_host = "localhost:6379"
+ config.email_social_alias = 'social@jamkazam.com'
config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
config.email_generic_from = 'nobody@jamkazam.com'
config.email_smtp_address = 'smtp.sendgrid.net'
diff --git a/admin/config/environments/development.rb b/admin/config/environments/development.rb
index b449898e8..8ae1ae4ed 100644
--- a/admin/config/environments/development.rb
+++ b/admin/config/environments/development.rb
@@ -46,4 +46,5 @@ JamAdmin::Application.configure do
config.email_generic_from = 'nobody-dev@jamkazam.com'
config.email_alerts_alias = 'alerts-dev@jamkazam.com'
+ config.email_social_alias = 'social-dev@jamkazam.com'
end
diff --git a/db/up/video_recording.sql b/db/up/video_recording.sql
index 44a976d30..46502b104 100644
--- a/db/up/video_recording.sql
+++ b/db/up/video_recording.sql
@@ -1 +1,3 @@
-ALTER TABLE recordings ADD video BOOLEAN NOT NULL DEFAULT FALSE;
\ No newline at end of file
+ALTER TABLE recordings ADD video BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE user_authorizations ADD refresh_token VARCHAR;
+ALTER TABLE recordings ADD external_video_id VARCHAR;
\ No newline at end of file
diff --git a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb
index f14d8e424..abe1b9564 100644
--- a/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb
+++ b/ruby/lib/jam_ruby/app/mailers/admin_mailer.rb
@@ -1,6 +1,6 @@
module JamRuby
# sends out a boring ale
- class AdminMailer < ActionMailer::Base
+ class AdminMailer < ActionMailer::Base
include SendGrid
@@ -20,6 +20,14 @@ module JamRuby
subject: options[:subject])
end
+ def social(options)
+ mail(to: APP_CONFIG.email_social_alias,
+ from: APP_CONFIG.email_generic_from,
+ body: options[:body],
+ content_type: "text/plain",
+ subject: options[:subject])
+ end
+
def recurly_alerts(user, options)
body = options[:body]
diff --git a/ruby/lib/jam_ruby/models/recording.rb b/ruby/lib/jam_ruby/models/recording.rb
index 1ec43b9aa..0fbf186e8 100644
--- a/ruby/lib/jam_ruby/models/recording.rb
+++ b/ruby/lib/jam_ruby/models/recording.rb
@@ -701,6 +701,10 @@ module JamRuby
self.save(:validate => false)
end
+ def add_video_data(data)
+ Recording.where(id: self.id).update_all(external_video_id: data[:video_id])
+ end
+
def add_timeline(timeline)
global = timeline["global"]
raise JamArgumentError, "global must be specified" unless global
diff --git a/ruby/lib/jam_ruby/models/user_authorization.rb b/ruby/lib/jam_ruby/models/user_authorization.rb
index e83bb8db4..7dfa2168c 100644
--- a/ruby/lib/jam_ruby/models/user_authorization.rb
+++ b/ruby/lib/jam_ruby/models/user_authorization.rb
@@ -1,7 +1,7 @@
module JamRuby
class UserAuthorization < ActiveRecord::Base
- attr_accessible :provider, :uid, :token, :token_expiration, :secret, :user
+ attr_accessible :provider, :uid, :token, :token_expiration, :secret, :user, :refresh_token
self.table_name = "user_authorizations"
@@ -12,6 +12,31 @@ module JamRuby
validates_uniqueness_of :uid, scope: :provider
# token, secret, token_expiration can be missing
+ def self.refreshing_google_auth(user)
+ auth = self.where(:user_id => user.id)
+ .where(:provider => 'google_login')
+ .limit(1).first
+
+ # if we have an auth that will expire in less than 10 minutes
+ if auth && auth.refresh_token && auth.token_expiration < Time.now - 60 * 10
+
+ oauth_client = OAuth2::Client.new(
+ Rails.application.config.google_client_id, Rails.application.config.google_secret,
+ :site => "https://accounts.google.com",
+ :token_url => "/o/oauth2/token",
+ :authorize_url => "/o/oauth2/auth")
+ access_token = OAuth2::AccessToken.from_hash(oauth_client, {:refresh_token => auth.refresh_token})
+ access_token = access_token.refresh!
+ auth.token = access_token.token
+ puts "access_token #{Time.now + access_token.expires_in} #{access_token.expires_in}"
+ auth.token_expiration = Time.now + access_token.expires_in
+ auth.save
+ auth
+ else
+ auth
+ end
+ end
+
def self.google_auth(user)
self
.where(:user_id => user.id)
diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb
index d909b57d2..e33df4093 100644
--- a/ruby/spec/support/utilities.rb
+++ b/ruby/spec/support/utilities.rb
@@ -7,6 +7,10 @@ def app_config
'http://localhost:3333'
end
+ def email_social_alias
+ 'social@jamkazam.com'
+ end
+
def email_alerts_alias
'alerts@jamkazam.com'
end
diff --git a/web/Gemfile b/web/Gemfile
index 49217de8a..dbf0f8454 100644
--- a/web/Gemfile
+++ b/web/Gemfile
@@ -41,12 +41,12 @@ gem 'eventmachine', '1.0.4'
gem 'faraday', '~>0.9.0'
gem 'amqp', '0.9.8'
gem 'logging-rails', :require => 'logging/rails'
-gem 'omniauth', '1.1.1'
-gem 'omniauth-facebook', '1.4.1'
+gem 'omniauth'
+gem 'omniauth-facebook'
gem 'omniauth-twitter'
-gem 'omniauth-google-oauth2', '0.2.1'
-gem 'google-api-client', '0.7.1'
-gem 'google-api-omniauth', '0.1.1'
+gem 'omniauth-google-oauth2'
+gem 'google-api-client' #, '0.7.1'
+#gem 'google-api-omniauth' #, '0.1.1'
gem 'signet', '0.5.0'
gem 'twitter'
gem 'fb_graph', '2.5.9'
diff --git a/web/app/assets/javascripts/dialog/deleteVideoConfirmDialog.js b/web/app/assets/javascripts/dialog/deleteVideoConfirmDialog.js
new file mode 100644
index 000000000..8b6c06811
--- /dev/null
+++ b/web/app/assets/javascripts/dialog/deleteVideoConfirmDialog.js
@@ -0,0 +1,69 @@
+(function (context, $) {
+
+ "use strict";
+ context.JK = context.JK || {};
+ context.JK.DeleteVideoConfirmDialog = function (app) {
+ var logger = context.JK.logger;
+ var rest = context.JK.Rest();
+ var recordingId = null;
+ var $dialog = null;
+ var $deleteFromDiskChkBox = null;
+ var $deleteBtn = null;
+ var deleting = false;
+ function resetForm() {
+
+ }
+
+ function beforeShow(args) {
+
+ recordingId = args.d1;
+
+ if(!recordingId) throw "recordingId must be specified";
+
+ $dialog.data('result', null);
+ deleting = false;
+
+ }
+
+ function afterHide() {
+
+ }
+
+ function attemptDelete() {
+ if(deleting) return;
+
+ deleting = true;
+
+ rest.deleteRecordingVideoData(recordingId)
+ .done(function(){
+ $dialog.data('result', true);
+ app.layout.closeDialog('delete-video-confirm-dialog');
+ })
+ .fail(app.ajaxError)
+ }
+
+ function events() {
+ $deleteBtn.click(attemptDelete);
+ }
+
+
+ function initialize() {
+ var dialogBindings = {
+ 'beforeShow': beforeShow,
+ 'afterHide': afterHide
+ };
+
+ app.bindDialog('delete-video-confirm-dialog', dialogBindings);
+
+ $dialog = $('#deleteVideoConfirmDialog');
+ $deleteFromDiskChkBox = $dialog.find('.delete-from-disk');
+ $deleteBtn = $dialog.find('.delete-btn');
+
+ events();
+
+ context.JK.checkbox($deleteFromDiskChkBox);
+ };
+
+ this.initialize = initialize;
+ }
+})(window, jQuery);
\ No newline at end of file
diff --git a/web/app/assets/javascripts/dialog/editRecordingDialog.js b/web/app/assets/javascripts/dialog/editRecordingDialog.js
index 9aac9d33a..409318c0a 100644
--- a/web/app/assets/javascripts/dialog/editRecordingDialog.js
+++ b/web/app/assets/javascripts/dialog/editRecordingDialog.js
@@ -14,7 +14,7 @@
var $isPublic = null;
var $saveBtn = null;
var $deleteBtn = null;
-
+ var $videoField = null;
var updating = false;
var deleting = false;
diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
index c9440dd80..9cd5c2ab8 100644
--- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
+++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
@@ -8,6 +8,8 @@
var playbackControls = null;
var recording = null; // deferred object
var $dialog = null;
+ var $saveVideoCheckbox = null
+ var $uploadToYoutube = null
function resetForm() {
// remove all display errors
@@ -156,6 +158,7 @@
window._oauth_callback = function() {
window._oauth_win.close()
+ logger.debug("closing window")
setGoogleAuthState()
}
return false;
@@ -167,6 +170,8 @@
var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked')
+ upload_to_youtube = false // don't prevent user from getting through dialog because popup now handles auth
+
if (upload_to_youtube) {
$.ajax({
type: "GET",
@@ -197,6 +202,7 @@
var save_video = $('#recording-finished-dialog form input[name=save_video]').is(':checked')
var upload_to_youtube = $('#recording-finished-dialog form input[name=upload_to_youtube]').is(':checked')
+ var recording_id = recording.id
rest.claimRecording({
id: recording.id,
name: name,
@@ -210,6 +216,11 @@
$dialog.data('result', {keep:true});
app.layout.closeDialog('recordingFinished');
context.JK.GA.trackMakeRecording();
+ if(save_video && upload_to_youtube) {
+ // you have to have elected to save video to have upload to youtube have any effect
+ context.VideoUploaderActions.showUploader(recording_id);
+ }
+
})
.fail(function (jqXHR) {
if (jqXHR.status == 422) {
@@ -296,9 +307,9 @@
// Check for google authorization using AJAX and show/hide the
// google login button / "signed in" label as appropriate:
- $(window).on('focus', function() {
+ /**$(window).on('focus', function() {
setGoogleAuthState();
- });
+ }); */
}
function setGoogleAuthState() {
@@ -333,10 +344,24 @@
}
function initializeButtons() {
+ $saveVideoCheckbox = $('#recording-finished-dialog input[name="save_video"]')
+ $uploadToYoutube = $('#recording-finished-dialog input[name="upload_to_youtube"]')
var isPublic = $('#recording-finished-dialog input[name="is_public"]');
context.JK.checkbox(isPublic);
- context.JK.checkbox($('#recording-finished-dialog input[name="save_video"]'));
- context.JK.checkbox($('#recording-finished-dialog input[name="upload_to_youtube"]'));
+ context.JK.checkbox($saveVideoCheckbox);
+ context.JK.checkbox($uploadToYoutube);
+
+ $saveVideoCheckbox.on('ifChanged', function() {
+ var saveVideoToDisk = $saveVideoCheckbox.is(':checked')
+
+ if(saveVideoToDisk) {
+ $uploadToYoutube.iCheck('enable')
+ }
+ else {
+ $uploadToYoutube.iCheck('disable')
+ }
+
+ })
}
function initialize() {
diff --git a/web/app/assets/javascripts/feedHelper.js b/web/app/assets/javascripts/feedHelper.js
index cdf32c7f9..d7ce6765f 100644
--- a/web/app/assets/javascripts/feedHelper.js
+++ b/web/app/assets/javascripts/feedHelper.js
@@ -471,6 +471,43 @@
$controls.data('server-info', feed.mix) // for recordingUtils helper methods
$controls.data('view-context', 'feed')
+ // tack on video if available
+ if(feed.external_video_id) {
+
+ var $videoWrapper = $feedItem.find('.video-wrapper')
+ var $videoContainer = $feedItem.find('.video-container')
+ if(gon.isNativeClient) {
+ var $embed = $('' +
+ '
' +
+ '
')
+ $videoContainer.append($embed).addClass('no-embed')
+ $videoWrapper.removeClass('hidden')
+ $embed.click(function() {
+ context.JK.popExternalLink($(this).attr('href'))
+ return false;
+ })
+ }
+ else {
+ var $embed = $('')
+ $videoContainer.append($embed)
+ $videoWrapper.removeClass('hidden')
+ }
+
+ if(feed.owner_id == context.JK.currentUserId) {
+ var $deleteVideoLink = $('delete video')
+ $deleteVideoLink.click(function() {
+ app.layout.showDialog('delete-video-confirm-dialog', { d1: feed.id }).one(EVENTS.DIALOG_CLOSED, function(e, data) {
+ if(!data.canceled && data.result) {
+ $deleteVideoLink.remove();
+ $videoWrapper.slideUp(1000);
+ }
+ })
+ return false;
+ })
+ $videoContainer.append($deleteVideoLink);
+ }
+ }
+
$('.timeago', $feedItem).timeago();
context.JK.prettyPrintElements($('.duration', $feedItem));
context.JK.setInstrumentAssetPath($('.instrument-icon', $feedItem));
@@ -552,6 +589,9 @@
// put the feed item on the page
renderFeed($feedItem);
+ if(feed.external_video_id) {
+ toggleRecordingDetails.call($('.details', $feedItem).get(0))
+ }
// these routines need the item to have height to work (must be after renderFeed)
$controls.listenRecording({recordingId: feed.id, claimedRecordingId: options.candidate_claimed_recording.id, sliderSelector:'.recording-slider', sliderBarSelector: '.recording-playback', currentTimeSelector:'.recording-current'});
$controls.bind('statechange.listenRecording', stateChangeRecording);
diff --git a/web/app/assets/javascripts/feed_item_recording.js b/web/app/assets/javascripts/feed_item_recording.js
index 8679b01c5..fe37c8b12 100644
--- a/web/app/assets/javascripts/feed_item_recording.js
+++ b/web/app/assets/javascripts/feed_item_recording.js
@@ -2,6 +2,7 @@
"use strict";
+ // http://img.youtube.com/vi/x-Oas9Sc6s0/hqdefault.jpg
context.JK = context.JK || {};
context.JK.FeedItemRecording = function($parentElement, options){
diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js
index 9f1e0a56b..ab4c37751 100644
--- a/web/app/assets/javascripts/jam_rest.js
+++ b/web/app/assets/javascripts/jam_rest.js
@@ -507,6 +507,28 @@
})
}
+ function getUserAuthorizations(options) {
+ var id = getId(options);
+
+ return $.ajax({
+ type: "GET",
+ dataType: "json",
+ url: "/api/users/" + id + '/authorizations',
+ processData: false
+ });
+ }
+
+ function getGoogleAuth(options) {
+ var id = getId(options);
+
+ return $.ajax({
+ type: "GET",
+ dataType: "json",
+ url: "/api/users/authorizations/google",
+ processData: false
+ });
+ }
+
function getUserDetail(options) {
var id = getId(options);
var detail = null;
@@ -1888,6 +1910,26 @@
});
}
+
+ function addRecordingVideoData(recordingId, data) {
+ return $.ajax({
+ type: "POST",
+ url: '/api/recordings/' + recordingId + '/video_data',
+ dataType: "json",
+ contentType: 'application/json',
+ data: JSON.stringify(data),
+ });
+ }
+
+ function deleteRecordingVideoData(recordingId) {
+ return $.ajax({
+ type: "DELETE",
+ url: '/api/recordings/' + recordingId + '/video_data',
+ dataType: "json",
+ contentType: 'application/json'
+ });
+ }
+
function createSignupHint(data) {
return $.ajax({
type: "POST",
@@ -1949,6 +1991,8 @@
this.cancelSession = cancelSession;
this.updateScheduledSession = updateScheduledSession;
this.getUserDetail = getUserDetail;
+ this.getUserAuthorizations = getUserAuthorizations;
+ this.getGoogleAuth = getGoogleAuth;
this.getUserProfile = getUserProfile;
this.getAffiliatePartnerData = getAffiliatePartnerData;
this.postAffiliatePartnerData = postAffiliatePartnerData;
@@ -2099,6 +2143,8 @@
this.validateUrlSite = validateUrlSite;
this.markRecordedBackingTrackSilent = markRecordedBackingTrackSilent;
this.addRecordingTimeline = addRecordingTimeline;
+ this.addRecordingVideoData = addRecordingVideoData;
+ this.deleteRecordingVideoData = deleteRecordingVideoData;
this.getMusicianSearchFilter = getMusicianSearchFilter;
this.postMusicianSearchFilter = postMusicianSearchFilter;
this.getBandSearchFilter = getBandSearchFilter;
diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js
index 118242dda..667ca9a6f 100644
--- a/web/app/assets/javascripts/react-components.js
+++ b/web/app/assets/javascripts/react-components.js
@@ -14,6 +14,7 @@
//= require ./react-components/stores/SessionOtherTracksStore
//= require ./react-components/stores/SessionMediaTracksStore
//= require ./react-components/stores/PlatformStore
+//= require ./react-components/stores/VideoUploaderStore
//= require_directory ./react-components/stores
//= require_directory ./react-components/mixins
//= require_directory ./react-components
diff --git a/web/app/assets/javascripts/react-components/PopupConfigureVideoGear.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupConfigureVideoGear.js.jsx.coffee
index 7ffd1e641..924567879 100644
--- a/web/app/assets/javascripts/react-components/PopupConfigureVideoGear.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/PopupConfigureVideoGear.js.jsx.coffee
@@ -55,7 +55,7 @@ if accessOpener
- CLOSE
+ CLOSE
`
diff --git a/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
new file mode 100644
index 000000000..3cce777c5
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
@@ -0,0 +1,209 @@
+context = window
+logger = context.JK.logger
+rest = context.JK.Rest()
+
+NoVideoRecordActive = 0
+WebCamRecordActive = 1
+ScreenRecordActive = 2
+
+mixins = []
+
+# make sure this is actually us opening the window, not someone else (by checking for MixerStore)
+
+# this check ensures we attempt to listen if this component is created in a popup
+reactContext = if window.opener? then window.opener else window
+
+accessOpener = false
+if window.opener?
+ try
+ m = window.opener.MixerStore
+ accessOpener = true
+ catch e
+ reactContext = window
+
+VideoUploaderStore = reactContext.VideoUploaderStore
+VideoUploaderActions = reactContext.VideoUploaderActions
+AppActions = reactContext.AppActions
+
+if accessOpener
+ mixins.push(Reflux.listenTo(VideoUploaderStore,"onVideoUploadChanged"))
+
+@PopupVideoUploader = React.createClass({
+ mixins: mixins
+
+ onVideoUploadChanged: (uploadState) ->
+ this.setState({uploadState: uploadState})
+
+ render: () ->
+
+ # there are a few initial states: if auth is null, we don't know yet (and are checking against the server)
+ # if auth is set, then we can show the upload btn
+ # and if auth == false, then we need the user to try and log in
+
+
+ if @state.recording
+
+ name = @state.recording.my?.name
+ description = @state.recording.my?.description
+ created = @state.recording.created_at
+
+ if @state.deleteMe
+ action = ``
+ instructions = `Save space and delete your video from your computer?
`
+ else if @state.recording.video_url?
+ action = `CLOSE`
+ instructions = `Your video has been uploaded previously. {this.state.uploadState.videoUrl}
`
+ else if @state.uploadState.errorReason
+ action = `UPLOAD`
+ instructions = `The upload failed. You can try again. (error reason={this.state.uploadState.errorReason})
`
+ else if @state.uploadState.done
+ action = `OK`
+ instructions = `Your video has been uploaded successfully. {this.state.uploadState.videoUrl}
`
+ else if @state.uploadState.paused
+ action = `RESUME`
+ instructions = `Press the RESUME button at any time to continue uploading your video.
`
+ else if @state.uploadState.uploading
+ action = ``
+ width = ((@state.uploadState.bytesSent / @state.uploadState.bytesTotal) * 100)
+ progressWidth = width.toString() + "%";
+ progressStyle = {width:progressWidth}
+ rounded = Math.round(width)
+ instructions = `Press the PAUSE button at any time to pause the upload of your video, or CANCEL to end this upload completely.
+
+
`
+ else if @state.auth == false
+ action = ``
+ instructions = `To upload this recording to YouTube, you must give JamKazam the necessary access to your YouTube account by clicking the button below.
`
+ else if @state.auth?
+ action = `UPLOAD`
+ instructions = `Press the UPLOAD button to start uploading your video to YouTube. When the upload is done, a link to your video will appear.
`
+ else
+ name = null
+ action = null
+ else
+ name = null
+ action = null
+
+ `
+
Upload Video
"{name}"
+
+ {instructions}
+
+ {action}
+
+
`
+
+ getInitialState: () ->
+ {auth: null, uploadState: VideoUploaderStore.getState(), deleteMe: false}
+
+ watchVideo: (e) ->
+ e.preventDefault()
+ $link = $(e.target)
+ AppActions.openExternalUrl($link.attr('href'))
+
+ onNextToDeletion: (e) ->
+ e.preventDefault();
+ @setState({deleteMe: true})
+
+ onCloseRequested: (e) ->
+ e.preventDefault()
+ window.close()
+
+ onDeleteVideo: (e) ->
+ e.preventDefault();
+ VideoUploaderActions.delete(gon.recording_id)
+ window.close()
+
+ 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 = @oauthCallback
+
+ oauthCallback: () ->
+ window._oauth_win.close()
+ @checkAuth()
+
+ onUploadRequested: (e) ->
+ e.preventDefault()
+ VideoUploaderActions.uploadVideo(gon.recording_id)
+
+ onResumeRequested: (e) ->
+ e.preventDefault()
+ VideoUploaderActions.resume(gon.recording_id)
+
+ onPauseRequested: (e) ->
+ e.preventDefault()
+ VideoUploaderActions.pause(gon.recording_id)
+
+ onCancelRequested: (e) ->
+ e.preventDefault()
+ VideoUploaderActions.cancel(gon.recording_id)
+
+ checkAuth:() ->
+ rest.getGoogleAuth()
+ .done((auth) =>
+ if auth.auth?
+ @setState({auth: auth.auth})
+ else
+ @setState({auth: false})
+ )
+ .fail(() =>
+ @setState({errorReason: 'Unable to fetch user authorization'})
+ )
+
+ checkRecording:() ->
+ rest.getRecording({id: gon.recording_id})
+ .done((response) =>
+ logger.debug("recording info fetched #{response}")
+ @setState({recording:response})
+ )
+ .fail((jqXHR) =>
+ logger.debug("recording info fetch failed #{jqXHR.responseText}")
+ @setState({errorReason: 'Unable to fetch recording info'})
+ )
+
+ componentDidMount: () ->
+
+ $(window).unload(@windowUnloaded)
+
+
+ @checkAuth()
+ @checkRecording()
+ VideoUploaderActions.newVideo(gon.recording_id)
+
+ @resizeWindow()
+
+ # this is necessary due to whatever the client's rendering behavior is.
+ setTimeout(@resizeWindow, 300)
+
+ componentDidUpdate: () ->
+ @resizeWindow()
+ setTimeout(@resizeWindow, 1000)
+
+ resizeWindow: () =>
+ $container = $('#minimal-container')
+ width = $container.width()
+ height = $container.height()
+
+ # there is 20px or so of unused space at the top of the page. can't figure out why it's there. (above #minimal-container)
+ #mysteryTopMargin = 20
+ mysteryTopMargin = 0
+ # deal with chrome in real browsers
+ offset = (window.outerHeight - window.innerHeight) + mysteryTopMargin
+
+ # handle native client chrome that the above outer-inner doesn't catch
+ #if navigator.userAgent.indexOf('JamKazam') > -1
+
+ #offset += 25
+
+ width = 100 if width < 100
+ height = 100 if height < 100
+
+ window.resizeTo(width, height + offset)
+
+ windowUnloaded: () ->
+ VideoUploaderActions.uploaderClosed()
+})
\ No newline at end of file
diff --git a/web/app/assets/javascripts/react-components/actions/VideoUploaderActions.js.coffee b/web/app/assets/javascripts/react-components/actions/VideoUploaderActions.js.coffee
new file mode 100644
index 000000000..e2fcf6804
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/actions/VideoUploaderActions.js.coffee
@@ -0,0 +1,12 @@
+context = window
+
+@VideoUploaderActions = Reflux.createActions({
+ uploadVideo: {},
+ pause: {},
+ resume: {},
+ cancel: {},
+ newVideo: {},
+ showUploader: {},
+ uploaderClosed: {}
+ delete: {}
+})
\ No newline at end of file
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 684013ec9..d3a57900c 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
@@ -50,14 +50,23 @@ logger = context.JK.logger
onStoppingRecording: (details) ->
details.cause = 'stopping'
+
this.trigger(details)
onStoppedRecording: (details) ->
details.cause = 'stopped'
+
+ if @recordingWindow?
+ @recordingWindow.close()
+
this.trigger(details)
onAbortedRecording: (details) ->
details.cause = 'aborted'
+
+ if @recordingWindow?
+ @recordingWindow.close()
+
this.trigger(details)
onOpenRecordingControls: () ->
diff --git a/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee b/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
new file mode 100644
index 000000000..cf6f6f375
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
@@ -0,0 +1,211 @@
+$ = jQuery
+context = window
+logger = context.JK.logger
+rest = context.JK.Rest();
+
+VideoUploaderActions = @VideoUploaderActions
+
+
+@VideoUploaderStore = Reflux.createStore(
+ {
+ listenables: VideoUploaderActions
+ logger: context.JK.logger
+ preparingUpload: false
+ uploading: false
+ paused: false
+ recordingId: null
+ videoUrl: null
+ bytesSent: 0
+ bytesTotal: 1
+ errorReason: null
+ errorDetail: null
+ state: null
+ done: false
+
+ init: ->
+ # Register with the app store to get @app
+ this.listenTo(context.AppStore, this.onAppInit)
+ @state = {uploading: @uploading, recordingId: @recordingId, videoUrl: @videoUrl, paused: @paused, preparingUpload: @preparingUpload, bytesSent: @bytesSent, bytesTotal: @bytesTotal, errorReason: @errorReason, errorDetail: @errorDetail, done: @done }
+
+ onAppInit: (app) ->
+ @app = app
+
+ triggerState: () ->
+ @state = {uploading: @uploading, recordingId: @recordingId, videoUrl: @videoUrl, paused: @paused, preparingUpload: @preparingUpload, bytesSent: @bytesSent, bytesTotal: @bytesTotal, errorReason: @errorReason, errorDetail: @errorDetail, done: @done}
+ @trigger(@state)
+
+ getState: () ->
+ @state
+
+ onPause: () ->
+ if @uploading
+ @uploading = false
+ @paused = true
+ context.jamClient.pauseVideoUpload()
+ @triggerState()
+
+ onResume: () ->
+ if @paused && @recordingId?
+ @uploading = true
+ @paused = false
+ context.jamClient.resumeVideoUpload()
+ @triggerState()
+ else
+ if @uploading
+ @app.layout.notify({title: 'Already uploading', text: "A video is already being uploaded."})
+ else
+ @app.layout.notify({title: 'Nothing to resume', text: "No upload to resume."})
+
+ onCancel: () ->
+ if @uploading
+ @uploading = false
+ context.jamClient.cancelVideoUpload()
+ @triggerState()
+
+ onDelete: (recordingId) ->
+ context.jamClient.deleteVideo(recordingId);
+
+ onNewVideo: (recordingId) ->
+
+ @onCancel(recordingId)
+
+ @done = false
+ @paused = false
+ @bytesSent = 0
+ @bytesTotal = 1
+ @errorReason = null
+ @errorDetail = null
+ @videoUrl = null
+ @triggerState()
+
+ onShowUploader: (recordingId) ->
+
+ if @childWindow?
+ logger.debug("showUploader popup being closed automatically")
+ @childWindow.close()
+ @childWindow = null
+
+ @childWindow = window.open("/popups/video/upload/" + recordingId, 'Video Uploader', 'scrollbars=yes,toolbar=no,status=no,height=155,width=350')
+
+ onUploaderClosed: () ->
+
+ if @childWindow?
+ @childWindow = null
+
+ onUploadVideo: (recordingId) ->
+
+ if @uploading || @preparingUpload
+ logger.debug("ignoring upload request")
+ return
+
+ @preparingUpload = true
+ @onNewVideo()
+
+ rest.getRecording({id:recordingId})
+ .done((response) =>
+ claim = response.my
+
+ privateStatus = 'private'
+ privateStatus = 'public' if claim.is_public
+
+ if claim?
+ videoInfo = {
+ "snippet": {
+ "title": claim.name,
+ "description": claim.description,
+ "tags": ["JamKazam"],
+ "categoryId": 10 # music
+ },
+ "status": {
+ "privacyStatus": privateStatus,
+ "embeddable": true,
+ "license": "youtube"
+ }
+ }
+
+ rest.getUserAuthorizations()
+ .done((response) =>
+
+ # http://localhost:3000/popups/video/upload/d25dbe8e-a066-4ea0-841d-16872c713fc9
+ youtube_auth = null
+ for authorization in response.authorizations
+ if authorization.provider == 'google_login'
+ youtube_auth = authorization.token
+ break
+
+ if youtube_auth?
+ logger.debug("calling uploadVideo(#{recordingId}, #{youtube_auth}, #{videoInfo})")
+ result = context.jamClient.uploadVideo(recordingId, youtube_auth, JSON.stringify(videoInfo),
+ "VideoUploaderStore.clientUploadCallback",
+ "VideoUploaderStore.clientDoneCallback",
+ "VideoUploaderStore.clientFailCallback")
+
+ if result.error
+ @preparingUpload = false
+ @triggerState()
+ @app.layout.notify({title: 'Unable to upload video', text: 'Application error: ' + result.error})
+ else
+ @preparingUpload = false
+ @videoUrl = null
+ @uploading = true
+ @recordingId = recordingId
+ @triggerState()
+
+
+ else
+ @preparingUpload = false
+ @triggerState()
+ @app.layout.notify({title: 'No Authorization Yet for YouTube', text: 'Youtube authorization still needed'})
+
+ )
+ .fail((jqXHR) =>
+ @preparingUpload = false
+ @triggerState()
+ @app.layout.notifyServerError(jqXHR, 'Unable to fetch user authorizations')
+ )
+
+ else
+ @preparingUpload = false
+ @triggerState()
+ @app.layout.notify({title: "You do not have a claim to this recording", text: "If this is in error, contact support@jamkazam.com."})
+
+ )
+ .fail((jqXHR) =>
+ @preparingUpload = false
+ @triggerState()
+ @app.layout.notifyServerError(jqXHR, 'Unable to fetch recording information')
+ )
+
+ clientUploadCallback: (bytesSent, bytesTotal) ->
+ logger.debug("bytesSent: #{bytesSent} bytesTotal: #{bytesTotal}")
+ VideoUploaderStore.bytesSent = Number(bytesSent)
+ VideoUploaderStore.bytesTotal = Number(bytesTotal)
+ VideoUploaderStore.triggerState()
+
+ clientDoneCallback: (video_id) ->
+ console.log
+ logger.debug("client uploaded video successfully to #{video_id}")
+ VideoUploaderStore.uploading = false
+
+ VideoUploaderStore.videoUrl = "https://www.youtube.com/watch?v=#{video_id}"
+
+ rest.addRecordingVideoData(VideoUploaderStore.recordingId, {video_id: video_id})
+ .fail(() =>
+ VideoUploaderStore.app.layout.notify({title: 'Sync Error', text:'Unable to notify server about uploaded video'})
+ )
+
+ VideoUploaderStore.recordingId = null
+ VideoUploaderStore.done = true
+ VideoUploaderStore.triggerState()
+
+ clientFailCallback: (reason, detail) =>
+ logger.warn("client failed to video upload #{reason}, #{detail}")
+ VideoUploaderStore.uploading = false
+ VideoUploaderStore.errorReason = reason
+ VideoUploaderStore.errorDetail = detail
+
+ VideoUploaderStore.triggerState()
+ }
+)
+
+@VideoUploaderStore
\ 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 a3122282e..b452eb4f8 100644
--- a/web/app/assets/javascripts/sync_viewer.js.coffee
+++ b/web/app/assets/javascripts/sync_viewer.js.coffee
@@ -815,6 +815,14 @@ context.JK.SyncViewer = class SyncViewer
$uploadState.addClass('is-native-client') if gon.isNativeClient
$track
+ openVideo: (e) =>
+ $link = $(e.target)
+ recordingId = $link.closest('.details').attr('data-recording-id')
+
+ if !context.jamClient.openVideo(recordingId)
+ @app.layout.notify({title: 'No Video Found', text: 'Did you delete this recording earlier from your computer?'})
+ return false
+
exportRecording: (e) =>
$export = $(e.target)
if context.SessionStore.inSession()
@@ -899,6 +907,10 @@ context.JK.SyncViewer = class SyncViewer
# create a virtual mix so that the UI is consistent
$wrapper.append(this.createMix('fake', recordingInfo))
+
+ if recordingInfo.video
+ $wrapper.find('a.open-video').click(this.openVideo).removeClass('hidden')
+
$wrapper.find('a.export').click(this.exportRecording)
$wrapper.find('a.delete').click(this.deleteRecording)
diff --git a/web/app/assets/stylesheets/dialogs/deleteVideoConfirmDialog.css.scss b/web/app/assets/stylesheets/dialogs/deleteVideoConfirmDialog.css.scss
new file mode 100644
index 000000000..da56f3597
--- /dev/null
+++ b/web/app/assets/stylesheets/dialogs/deleteVideoConfirmDialog.css.scss
@@ -0,0 +1,13 @@
+@import "client/common";
+
+#deleteVideoConfirmDialog {
+
+ min-height: 180px;
+ width: 400px;
+
+ .buttons {
+ float:right;
+ clear:both;
+ margin-top:20px;
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/minimal/video_uploader.css.scss b/web/app/assets/stylesheets/minimal/video_uploader.css.scss
new file mode 100644
index 000000000..12b109b7b
--- /dev/null
+++ b/web/app/assets/stylesheets/minimal/video_uploader.css.scss
@@ -0,0 +1,58 @@
+@import "client/common";
+
+body.video-upload {
+
+ position: relative;
+ color: $ColorTextTypical;
+
+ #minimal-container {
+ padding-bottom: 20px;
+ height:240px;
+ }
+
+ .video-uploader {
+ padding-left: 30px;
+ padding-right:30px;
+ }
+
+ h3 {
+ margin-top:20px;
+ font-size:16px;
+ font-weight:bold;
+ margin-bottom:20px;
+ text-align:center;
+ line-height:125%;
+ }
+
+ .control-holder {
+ margin: 20px 0 20px;
+ text-align:center;
+ padding-bottom:20px;
+
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ left: 0;
+ }
+
+ .progress-bar {
+ background-color:#ED3618;
+ border:solid 1px #000;
+ height:20px;
+ display:block;
+ @include border_box_sizing;
+ margin:20px 0;
+ position:relative;
+ }
+
+ .percentage-progress {
+ position:absolute;
+ right:-32px;
+ }
+
+ .video-url {
+ text-align:center;
+ display:block;
+ margin:20px 0;
+ }
+}
\ No newline at end of file
diff --git a/web/app/assets/stylesheets/users/syncViewer.css.scss b/web/app/assets/stylesheets/users/syncViewer.css.scss
index dcdae64e4..096c660a6 100644
--- a/web/app/assets/stylesheets/users/syncViewer.css.scss
+++ b/web/app/assets/stylesheets/users/syncViewer.css.scss
@@ -192,7 +192,7 @@
@include border_box_sizing;
@include vertical-align-column;
- .export {
+ .export, .open-video {
float:right;
margin-right:10px;
font-size:12px;
diff --git a/web/app/assets/stylesheets/web/recordings.css.scss b/web/app/assets/stylesheets/web/recordings.css.scss
index 20b9aca35..539875c33 100644
--- a/web/app/assets/stylesheets/web/recordings.css.scss
+++ b/web/app/assets/stylesheets/web/recordings.css.scss
@@ -8,6 +8,46 @@
position:relative;
}
+.feed-entry {
+
+ .video-container {
+ width: 100%;
+ padding-bottom: 53.33%;
+ margin: 30px 0 10px;
+
+ &.no-embed {
+ padding-bottom:0;
+ }
+ .recording-controls {
+ width:100%;
+ }
+ }
+
+ .delete-video {
+ display:block;
+ text-align:center;
+ font-size:12px;
+ text-decoration:underline;
+ }
+
+ .video-thumbnail {
+ width:100%;
+ //padding-top:53.33%;
+ //margin:25px 0;
+ @include border_box_sizing;
+ position:relative;
+
+
+ img.video {
+ width:100%;
+ }
+ img.play {
+ position:absolute;
+ width:15%;
+ left: 42.5%;
+ }
+ }
+}
.landing-details .recording-controls, .landing-details .session-controls {
background-color:#242323;
diff --git a/web/app/controllers/api_recordings_controller.rb b/web/app/controllers/api_recordings_controller.rb
index 77358b13c..9ce09c645 100644
--- a/web/app/controllers/api_recordings_controller.rb
+++ b/web/app/controllers/api_recordings_controller.rb
@@ -1,7 +1,7 @@
class ApiRecordingsController < ApiController
before_filter :api_signed_in_user, :except => [ :add_like ]
- before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim, :add_timeline ]
+ before_filter :lookup_recording, :only => [ :show, :stop, :claim, :discard, :keep, :delete_claim, :add_timeline, :add_video_data, :delete_video_data ]
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 ]
@@ -384,6 +384,32 @@ class ApiRecordingsController < ApiController
render :json => {}, :status => 200
end
+ def add_video_data
+ @recording.add_video_data(params)
+
+ video_id = params[:video_id]
+
+ video_url = "https://www.youtube.com/watch?v=#{video_id}"
+
+ body = ""
+ body << "Youtube URL: #{video_url}\n\n"
+ body << "User: " + current_user.admin_url + "\n\n"
+ body << "Recording Landing: #{recording_detail_url(@recording.id)}\n"
+
+ AdminMailer.alerts({
+ subject:"Video Uploaded by #{current_user.name}",
+ body:body
+ }).deliver
+
+ render :json => {}, :status => 200
+ end
+
+ def delete_video_data
+ @recording.add_video_data({:video_id => nil})
+
+ render :json => {}, :status => 200
+ end
+
private
def lookup_recording
diff --git a/web/app/controllers/api_users_controller.rb b/web/app/controllers/api_users_controller.rb
index 17d96fd5d..955f157ec 100644
--- a/web/app/controllers/api_users_controller.rb
+++ b/web/app/controllers/api_users_controller.rb
@@ -2,8 +2,8 @@ require 'sanitize'
class
ApiUsersController < ApiController
- before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data]
- before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete,
+ before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth]
+ before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete, :authorizations,
:liking_create, :liking_destroy, # likes
:following_create, :following_show, :following_destroy, # followings
:recording_update, :recording_destroy, # recordings
@@ -36,6 +36,17 @@ ApiUsersController < ApiController
respond_with @user, responder: ApiResponder, :status => 200
end
+ def authorizations
+
+ @user = current_user
+ respond_with @user, responder: ApiResponder, :status => 200
+ end
+
+ def google_auth
+ @user = current_user
+ respond_with @user, responder: ApiResponder, :status => 200
+ end
+
def profile_show
@profile = User.includes([{musician_instruments: :instrument},
{band_musicians: :user},
diff --git a/web/app/controllers/popups_controller.rb b/web/app/controllers/popups_controller.rb
index e1a6c95c4..427bff4f9 100644
--- a/web/app/controllers/popups_controller.rb
+++ b/web/app/controllers/popups_controller.rb
@@ -22,4 +22,10 @@ class PopupsController < ApplicationController
@video_id = params[:id]
render :layout => "minimal"
end
+
+ def video_upload
+ @recording_id = params[:recording_id]
+ gon.recording_id = @recording_id
+ render :layout => "minimal"
+ end
end
\ No newline at end of file
diff --git a/web/app/controllers/sessions_controller.rb b/web/app/controllers/sessions_controller.rb
index 931960fdb..a7511c4d1 100644
--- a/web/app/controllers/sessions_controller.rb
+++ b/web/app/controllers/sessions_controller.rb
@@ -88,7 +88,9 @@ class SessionsController < ApplicationController
provider = auth_hash[:provider]
- if provider == 'twitter'
+ if provider == 'google_login'
+
+ elsif provider == 'twitter'
@user_authorization = current_user.build_twitter_authorization(auth_hash)
if !@user_authorization.save
# this is a very poorly styled page, but it's better than a server error.
@@ -164,22 +166,31 @@ class SessionsController < ApplicationController
return
end
- #authorization = UserAuthorization.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
+ authorization = UserAuthorization.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
# Always make and save a new authorization. This is because they expire, and honestly there's no cost
# to just making and saving it.
+
user_auth_hash = {
:provider => auth_hash[:provider],
:uid => auth_hash[:uid],
- :token => auth_hash[:credentials][:token],
+ :token => auth_hash[:credentials][:token],
+ :refresh_token => auth_hash[:credentials][:refresh_token],
:token_expiration => Time.at(auth_hash[:credentials][:expires_at]),
:secret => auth_hash[:credentials][:secret]
}
- #if authorization.nil?
+ if authorization.nil?
+
authorization = current_user.user_authorizations.build(user_auth_hash)
authorization.save
- #end
+
+ else
+ authorization.token = auth_hash[:credentials][:token]
+ authorization.token_expiration = Time.at(auth_hash[:credentials][:expires_at])
+ authorization.save
+ end
+
render 'oauth_complete', :layout => "landing"
end
diff --git a/web/app/views/api_claimed_recordings/show.rabl b/web/app/views/api_claimed_recordings/show.rabl
index 379d1cb96..4d21a2498 100644
--- a/web/app/views/api_claimed_recordings/show.rabl
+++ b/web/app/views/api_claimed_recordings/show.rabl
@@ -20,7 +20,7 @@ node :mix do |claimed_recording|
end
child(:recording => :recording) {
- attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :has_mix?, :mix_state, :when_will_be_discarded?, :jam_track_id, :jam_track_initiator_id
+ attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :has_mix?, :mix_state, :when_will_be_discarded?, :jam_track_id, :jam_track_initiator_id, :video
node :timeline do |recording|
recording.timeline ? JSON.parse(recording.timeline) : {}
diff --git a/web/app/views/api_feeds/show.rabl b/web/app/views/api_feeds/show.rabl
index 1bbe751cd..99fe1e7c8 100644
--- a/web/app/views/api_feeds/show.rabl
+++ b/web/app/views/api_feeds/show.rabl
@@ -78,7 +78,7 @@ glue :recording do
'recording'
end
- attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :has_mix?, :mix_state, :when_will_be_discarded?
+ attributes :id, :band, :owner_id, :created_at, :duration, :comment_count, :like_count, :play_count, :has_mix?, :mix_state, :when_will_be_discarded?, :video, :external_video_id
node do |recording|
{
diff --git a/web/app/views/api_recordings/show.rabl b/web/app/views/api_recordings/show.rabl
index 285d7bbb6..dbc488a27 100644
--- a/web/app/views/api_recordings/show.rabl
+++ b/web/app/views/api_recordings/show.rabl
@@ -1,6 +1,6 @@
object @recording
-attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :when_will_be_discarded?, :jam_track_id, :jam_track_initiator_id, :music_session_id, :music_session, :video
+attributes :id, :band, :created_at, :duration, :comment_count, :like_count, :play_count, :when_will_be_discarded?, :jam_track_id, :jam_track_initiator_id, :music_session_id, :music_session, :video, :external_video_id
node :fan_access do |recording|
recording.non_active_music_session.fan_access
@@ -69,7 +69,7 @@ child(:comments => :comments) {
node :my do |recording|
claim = recording.claim_for_user(current_user) || recording.candidate_claimed_recording
if claim
- { name: claim.name, description: claim.description, genre: claim.genre.id, genre_name: claim.genre.description, id: claim.id }
+ { name: claim.name, description: claim.description, genre: claim.genre.id, genre_name: claim.genre.description, id: claim.id, is_public: claim.is_public }
else
nil
end
diff --git a/web/app/views/api_users/authorizations.rabl b/web/app/views/api_users/authorizations.rabl
new file mode 100644
index 000000000..2441f997a
--- /dev/null
+++ b/web/app/views/api_users/authorizations.rabl
@@ -0,0 +1,6 @@
+object @user
+
+child :user_authorizations => :authorizations do
+ attributes :uid, :provider, :token, :secret
+end
+
diff --git a/web/app/views/api_users/google_auth.rabl b/web/app/views/api_users/google_auth.rabl
new file mode 100644
index 000000000..bf29c2857
--- /dev/null
+++ b/web/app/views/api_users/google_auth.rabl
@@ -0,0 +1,16 @@
+object @user
+
+node :auth do
+ auth = UserAuthorization.refreshing_google_auth(@user)
+
+ if auth
+ {
+ uid: auth.uid,
+ provider: auth.provider,
+ token: auth.token,
+ secret: auth.secret
+ }
+ else
+ nil
+ end
+end
diff --git a/web/app/views/clients/_sync_viewer_templates.html.slim b/web/app/views/clients/_sync_viewer_templates.html.slim
index 35640dfe4..9d6da2c3d 100644
--- a/web/app/views/clients/_sync_viewer_templates.html.slim
+++ b/web/app/views/clients/_sync_viewer_templates.html.slim
@@ -117,6 +117,8 @@ script type="text/template" id="template-sync-viewer-recording-wrapper-details"
| DELETE
a.export href="#"
| EXPORT
+ a.open-video.hidden href="#"
+ | VIDEO
script type="text/template" id="template-sync-viewer-hover-recorded-track"
.help-hover-recorded-tracks
diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb
index 4eb4f3797..962e21298 100644
--- a/web/app/views/clients/index.html.erb
+++ b/web/app/views/clients/index.html.erb
@@ -159,6 +159,9 @@
var editRecordingDialog = new JK.EditRecordingDialog(JK.app);
editRecordingDialog.initialize();
+ var deleteVideoDialog = new JK.DeleteVideoConfirmDialog(JK.app);
+ deleteVideoDialog.initialize();
+
var recordingFinishedDialog = new JK.RecordingFinishedDialog(JK.app);
recordingFinishedDialog.initialize();
JK.recordingFinishedDialog = recordingFinishedDialog
diff --git a/web/app/views/dialogs/_deleteVideoConfirmDialog.html.slim b/web/app/views/dialogs/_deleteVideoConfirmDialog.html.slim
new file mode 100644
index 000000000..c2a473fd7
--- /dev/null
+++ b/web/app/views/dialogs/_deleteVideoConfirmDialog.html.slim
@@ -0,0 +1,15 @@
+#deleteVideoConfirmDialog.dialog layout='dialog' layout-id='delete-video-confirm-dialog'
+ .content-head
+ = image_tag "content/icon_alert.png", {:width => 24, :height => 24, :class => 'content-icon' }
+ h1 Delete video from JamKazam?
+ .dialog-inner
+ .dialog-info
+ | This will delete this video from JamKazam, but not from YouTube. To delete this video from YouTube, you must go to your YouTube account as we cannot do this for you.
+ //.delete-from-disk
+ // input.delete-from-disk type="checkbox" value="delete-from-disk"
+ .buttons
+ .left
+ a.button-grey.cancel-btn layout-action='cancel' CANCEL
+ .right
+ a.button-orange.delete-btn DELETE
+ br clear='all'
\ No newline at end of file
diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml
index 03b806aba..45db4cb3f 100644
--- a/web/app/views/dialogs/_dialogs.html.haml
+++ b/web/app/views/dialogs/_dialogs.html.haml
@@ -43,3 +43,4 @@
= render 'dialogs/genreSelectorDialog'
= render 'dialogs/recordingSelectorDialog'
= render 'dialogs/soundCloudPlayerDialog'
+= render 'dialogs/deleteVideoConfirmDialog'
diff --git a/web/app/views/dialogs/_recordingFinishedDialog.html.haml b/web/app/views/dialogs/_recordingFinishedDialog.html.haml
index 4447f4d8f..3814e694d 100644
--- a/web/app/views/dialogs/_recordingFinishedDialog.html.haml
+++ b/web/app/views/dialogs/_recordingFinishedDialog.html.haml
@@ -26,14 +26,12 @@
%textarea#claim-recording-description.w100{:name => "description"}
-if (Rails.application.config.video_available=="full") || (current_user && current_user.admin)
.save-video.field.left{:purpose => "save_video"}
- %input{:name => "save_video", :type => "checkbox"}/
+ %input{:name => "save_video", :type => "checkbox", :checked => "checked"}/
%label{:for => "save_video"} Save Video to Computer
- .hidden.field.left{:purpose => "upload_to_youtube"}
+ .field.left{:purpose => "upload_to_youtube"}
%span
%input{:name => "upload_to_youtube", :type => "checkbox", :checked => "checked"}/
%label{:for => "upload_to_youtube"} Upload Video to YouTube
- %span
- = render(:partial => "shared/google_login")
.field.left{:purpose => "is_public"}
%input{:checked => "checked", :name => "is_public", :type => "checkbox"}/
%label{:for => "is_public"} Public Recording
diff --git a/web/app/views/popups/video_upload.html.slim b/web/app/views/popups/video_upload.html.slim
new file mode 100644
index 000000000..19a573c10
--- /dev/null
+++ b/web/app/views/popups/video_upload.html.slim
@@ -0,0 +1,3 @@
+- provide(:page_name, 'video-upload popup')
+- provide(:title, 'Video Upload')
+= react_component 'PopupVideoUploader', {}
\ No newline at end of file
diff --git a/web/app/views/sessions/oauth_complete.erb b/web/app/views/sessions/oauth_complete.erb
index e8c16289b..73e98237b 100644
--- a/web/app/views/sessions/oauth_complete.erb
+++ b/web/app/views/sessions/oauth_complete.erb
@@ -1,5 +1,7 @@
<% provide(:description, 'Finished authorization') %>
+This window will close momentarily and you can then continue.
+
diff --git a/web/app/views/users/_feed_recording_ajax.html.haml b/web/app/views/users/_feed_recording_ajax.html.haml
index 4ab4914b3..f410088d8 100644
--- a/web/app/views/users/_feed_recording_ajax.html.haml
+++ b/web/app/views/users/_feed_recording_ajax.html.haml
@@ -106,6 +106,9 @@
= '{% } %}'
= '{% }) %}'
+ .video-wrapper.hidden
+ .video-container
+
%br{:clear => "all"}/
%br/
diff --git a/web/config/application.rb b/web/config/application.rb
index 5b156a278..49724235a 100644
--- a/web/config/application.rb
+++ b/web/config/application.rb
@@ -239,6 +239,7 @@ if defined?(Bundler)
config.estimated_slow_mixdown_time = 80
config.num_packaging_nodes = 2
+ config.email_social_alias = 'social@jamkazam.com'
config.email_alerts_alias = 'alerts@jamkazam.com' # should be used for 'oh no' server down/service down sorts of emails
config.email_generic_from = 'nobody@jamkazam.com'
config.email_recurly_notice = 'recurly-alerts@jamkazam.com'
diff --git a/web/config/environments/development.rb b/web/config/environments/development.rb
index ad49ae6b7..22c95ff37 100644
--- a/web/config/environments/development.rb
+++ b/web/config/environments/development.rb
@@ -95,6 +95,7 @@ SampleApp::Application.configure do
config.video_available= ENV['VIDEO_AVAILABILITY'] || "full"
config.email_generic_from = 'nobody-dev@jamkazam.com'
config.email_alerts_alias = ENV['ALERT_EMAIL'] || 'alerts-dev@jamkazam.com'
+ config.email_social_alias = ENV['ALERT_EMAIL'] || 'social-dev@jamkazam.com'
config.guard_against_fraud = true
config.react.variant = :development
diff --git a/web/config/initializers/omniauth.rb b/web/config/initializers/omniauth.rb
index 25d74535d..e88af1289 100644
--- a/web/config/initializers/omniauth.rb
+++ b/web/config/initializers/omniauth.rb
@@ -1,6 +1,8 @@
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, Rails.application.config.facebook_app_id, Rails.application.config.facebook_app_secret, {name: "facebook", :scope => 'email,user_location'}
- provider :google_oauth2, Rails.application.config.google_client_id, Rails.application.config.google_secret, {name: "google_login", approval_prompt: '', scope: 'userinfo.email, userinfo.profile, https://www.google.com/m8/feeds'}
+ # add these back later if needed
+ # userinfo.email, userinfo.profile, https://www.google.com/m8/feeds,
+ provider :google_oauth2, Rails.application.config.google_client_id, Rails.application.config.google_secret, {name: "google_login", prompt: 'consent', scope: 'userinfo.email, https://www.googleapis.com/auth/youtube.upload, https://www.googleapis.com/auth/youtube'}
provider :twitter, Rails.application.config.twitter_app_id, Rails.application.config.twitter_app_secret, {x_auth_access_type: 'write' }
end
diff --git a/web/config/routes.rb b/web/config/routes.rb
index e14387176..1c0de28e6 100644
--- a/web/config/routes.rb
+++ b/web/config/routes.rb
@@ -148,6 +148,7 @@ SampleApp::Application.routes.draw do
match '/youtube/player', to: 'popups#youtube_player'
match '/how-to-use-video', to: 'popups#how_to_use_video'
match '/configure-video', to: 'popups#configure_video'
+ match '/video/upload/:recording_id', to: 'popups#video_upload'
end
scope '/corp' do
@@ -193,7 +194,7 @@ SampleApp::Application.routes.draw do
# music sessions
match '/sessions/:id/participants/legacy' => 'api_music_sessions#participant_create_legacy', :via => :post # can be removed when new Create Session comes in
match '/sessions/:id/participants' => 'api_music_sessions#participant_create', :via => :post
- match '/participants/:id' => 'api_music_sessions#participant_show', :via => :get, :as => 'api_session_participant_detail'
+ match '/participants/:id' => 'api_music_sessions#participant_show', :via => :tt, :as => 'api_session_participant_detail'
match '/participants/:id' => 'api_music_sessions#participant_delete', :via => :delete
match '/sessions/scheduled' => 'api_music_sessions#scheduled', :via => :get
match '/sessions/scheduled_rsvp' => 'api_music_sessions#scheduled_rsvp', :via => :get
@@ -297,12 +298,14 @@ SampleApp::Application.routes.draw do
match '/users' => 'api_users#index', :via => :get
match '/users' => 'api_users#create', :via => :post
match '/users/:id' => 'api_users#show', :via => :get, :as => 'api_user_detail'
+ match '/users/:id/authorizations' => 'api_users#authorizations', :via => :get
#match '/users' => 'api_users#create', :via => :post
match '/users/:id' => 'api_users#update', :via => :post
match '/users/:id' => 'api_users#delete', :via => :delete
match '/users/:id/calendar.ics' => 'api_users#calendar', :via => :get, :as => 'api_users_calendar_feed'
match '/users/confirm/:signup_token' => 'api_users#signup_confirm', :via => :post, :as => 'api_signup_confirmation'
match '/users/complete/:signup_token' => 'api_users#complete', as: 'complete', via: 'post'
+ match '/users/authorizations/google' => 'api_users#google_auth', :via => :get
match '/users/:id/set_password' => 'api_users#set_password', :via => :post
# recurly
@@ -523,6 +526,8 @@ SampleApp::Application.routes.draw do
match '/recordings/:id/likes' => 'api_recordings#add_like', :via => :post, :as => 'api_recordings_add_like'
match '/recordings/:id/discard' => 'api_recordings#discard', :via => :post, :as => 'api_recordings_discard'
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'
# Recordings - recorded_tracks
match '/recordings/:id/tracks/:track_id' => 'api_recordings#show_recorded_track', :via => :get, :as => 'api_recordings_show_recorded_track'
diff --git a/websocket-gateway/lib/jam_websockets/router.rb b/websocket-gateway/lib/jam_websockets/router.rb
index fd5202450..f69cfd4db 100644
--- a/websocket-gateway/lib/jam_websockets/router.rb
+++ b/websocket-gateway/lib/jam_websockets/router.rb
@@ -882,9 +882,9 @@ module JamWebsockets
end
def runaway_heartbeat(heartbeat, context)
- heartbeat_count = @heartbeat_tracker[context.user.id] || 0
+ heartbeat_count = @heartbeat_tracker[context.client.client_id] || 0
heartbeat_count += 1
- @heartbeat_tracker[context.user.id] = heartbeat_count
+ @heartbeat_tracker[context.client.client_id] = heartbeat_count
if heartbeat_count > (context.client_type == 'browser' ? @maximum_minutely_heartbeat_rate_browser : @maximum_minutely_heartbeat_rate_client)
@log.warn("user #{context.user} sending too many heartbeats: #{heartbeat_count}") if heartbeat_count % 100 == 0
From 509cc56cdb774983aa7083e08b786d3687a7daf9 Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Thu, 8 Oct 2015 07:50:20 -0500
Subject: [PATCH 2/9] * dont let refresh attempt bust API
---
ruby/lib/jam_ruby/models/user_authorization.rb | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/ruby/lib/jam_ruby/models/user_authorization.rb b/ruby/lib/jam_ruby/models/user_authorization.rb
index 7dfa2168c..10b126d75 100644
--- a/ruby/lib/jam_ruby/models/user_authorization.rb
+++ b/ruby/lib/jam_ruby/models/user_authorization.rb
@@ -20,6 +20,7 @@ module JamRuby
# if we have an auth that will expire in less than 10 minutes
if auth && auth.refresh_token && auth.token_expiration < Time.now - 60 * 10
+ begin
oauth_client = OAuth2::Client.new(
Rails.application.config.google_client_id, Rails.application.config.google_secret,
:site => "https://accounts.google.com",
@@ -27,8 +28,12 @@ module JamRuby
:authorize_url => "/o/oauth2/auth")
access_token = OAuth2::AccessToken.from_hash(oauth_client, {:refresh_token => auth.refresh_token})
access_token = access_token.refresh!
+ rescue Exception => e
+ # couldn't refresh; probably the user has revoked the app's rights
+ nil
+ end
+
auth.token = access_token.token
- puts "access_token #{Time.now + access_token.expires_in} #{access_token.expires_in}"
auth.token_expiration = Time.now + access_token.expires_in
auth.save
auth
From aa51114e8a2642b32e9b0573b842ea5e8a18153d Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Thu, 8 Oct 2015 11:36:16 -0500
Subject: [PATCH 3/9] * deal with no refresh token better
---
.../lib/jam_ruby/models/user_authorization.rb | 26 +++++++++----------
.../stores/VideoUploaderStore.js.coffee | 4 +++
2 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/ruby/lib/jam_ruby/models/user_authorization.rb b/ruby/lib/jam_ruby/models/user_authorization.rb
index 10b126d75..f4c1523eb 100644
--- a/ruby/lib/jam_ruby/models/user_authorization.rb
+++ b/ruby/lib/jam_ruby/models/user_authorization.rb
@@ -21,22 +21,22 @@ module JamRuby
if auth && auth.refresh_token && auth.token_expiration < Time.now - 60 * 10
begin
- oauth_client = OAuth2::Client.new(
- Rails.application.config.google_client_id, Rails.application.config.google_secret,
- :site => "https://accounts.google.com",
- :token_url => "/o/oauth2/token",
- :authorize_url => "/o/oauth2/auth")
- access_token = OAuth2::AccessToken.from_hash(oauth_client, {:refresh_token => auth.refresh_token})
- access_token = access_token.refresh!
+ oauth_client = OAuth2::Client.new(
+ Rails.application.config.google_client_id, Rails.application.config.google_secret,
+ :site => "https://accounts.google.com",
+ :token_url => "/o/oauth2/token",
+ :authorize_url => "/o/oauth2/auth")
+ access_token = OAuth2::AccessToken.from_hash(oauth_client, {:refresh_token => auth.refresh_token})
+ access_token = access_token.refresh!
+
+ auth.token = access_token.token
+ auth.token_expiration = Time.now + access_token.expires_in
+ auth.save
+ return auth
rescue Exception => e
# couldn't refresh; probably the user has revoked the app's rights
- nil
+ return nil
end
-
- auth.token = access_token.token
- auth.token_expiration = Time.now + access_token.expires_in
- auth.save
- auth
else
auth
end
diff --git a/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee b/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
index cf6f6f375..1a93887ba 100644
--- a/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
@@ -204,6 +204,10 @@ VideoUploaderActions = @VideoUploaderActions
VideoUploaderStore.errorReason = reason
VideoUploaderStore.errorDetail = detail
+ # if reason == "create_video_failed" && errorDetail = "401"
+ # then don't trigger state, instead ask server for a fresh token
+ #
+
VideoUploaderStore.triggerState()
}
)
From 4bdf555409a78f557427c277742c11aff4e52f83 Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Thu, 8 Oct 2015 11:45:18 -0500
Subject: [PATCH 4/9] * make height of google prompt bigger because we ask for
more
---
.../react-components/PopupVideoUploader.js.jsx.coffee | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
index 3cce777c5..d4ea2fe7b 100644
--- a/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
@@ -118,7 +118,7 @@ if accessOpener
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_win = window.open("/auth/google_login", "Log In to Google", "height=700,width=500,menubar=no,resizable=no,status=no");
window._oauth_callback = @oauthCallback
From c3097d4436ca24e1928855e25c04d03b7fe99e04 Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Thu, 8 Oct 2015 12:18:18 -0500
Subject: [PATCH 5/9] * VRFS-3625 - tell backend correctly abot keeping video
if user discards, and filter out recordingId of video
---
.../javascripts/dialog/recordingFinishedDialog.js | 14 ++++++++++----
web/app/assets/javascripts/recordingModel.js | 12 ++++++++++++
2 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
index 9cd5c2ab8..d350841e8 100644
--- a/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
+++ b/web/app/assets/javascripts/dialog/recordingFinishedDialog.js
@@ -115,9 +115,15 @@
if(recording && recording.video) {
var name = $('#recording-finished-dialog form input[name=name]').val();
name = name.replace(/[^A-Za-z0-9\-\ ]/g, '');
- var keep = $('#recording-finished-dialog form input[name=save_video]').is(':checked')
- logger.debug("VideoDecision rid:" + recording.id + ", name=" + name + ", keep=" + keep)
- context.jamClient.VideoDecision(recording.id, name, keep)
+
+
+ var saveToDisk = $('#recording-finished-dialog form input[name=save_video]').is(':checked')
+ var keepResult = $dialog.data('result');
+ keepResult = keepResult && keepResult.keep
+
+ logger.debug("VideoDecision rid:" + recording.id + ", name=" + name + ", keepResult=" + keepResult + ", saveToDisk=" + saveToDisk);
+
+ context.jamClient.VideoDecision(recording.id, name, keepResult && saveToDisk)
}
recording = null;
@@ -217,7 +223,7 @@
app.layout.closeDialog('recordingFinished');
context.JK.GA.trackMakeRecording();
if(save_video && upload_to_youtube) {
- // you have to have elected to save video to have upload to youtube have any effect
+ // you have to have elected to save video to have upload to youtube have
context.VideoUploaderActions.showUploader(recording_id);
}
diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js
index b74fb24d9..57d9eaf7c 100644
--- a/web/app/assets/javascripts/recordingModel.js
+++ b/web/app/assets/javascripts/recordingModel.js
@@ -271,6 +271,13 @@
}
function handleRecordingStopped(recordingId, result) {
+
+ if(recordingId == "video") {
+ return;
+ }
+
+ logger.debug("handleRecordingStopped " + recordingId, result)
+
var success = result.success;
var reason = result.reason;
var detail = result.detail;
@@ -315,6 +322,11 @@
}
function handleRecordingAborted(recordingId, result) {
+
+ if(recordingId == "video") {
+ return;
+ }
+
var success = result.success;
var reason = result.reason;
var detail = result.detail;
From 6ecf59152068c734f508875249cec002bc8cf7bb Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Thu, 8 Oct 2015 15:07:51 -0500
Subject: [PATCH 6/9] * VRFS-3629 - deal with upload failure correctly
---
.../react-components/PopupVideoUploader.js.jsx.coffee | 2 +-
.../react-components/stores/VideoUploaderStore.js.coffee | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee b/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
index d4ea2fe7b..2d3ec02c5 100644
--- a/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/PopupVideoUploader.js.jsx.coffee
@@ -55,7 +55,7 @@ if accessOpener
instructions = `Your video has been uploaded previously. {this.state.uploadState.videoUrl}
`
else if @state.uploadState.errorReason
action = `UPLOAD`
- instructions = `The upload failed. You can try again. (error reason={this.state.uploadState.errorReason})
`
+ instructions = `The upload failed. You can try again. ({this.state.uploadState.errorReason})
`
else if @state.uploadState.done
action = `OK`
instructions = `Your video has been uploaded successfully. {this.state.uploadState.videoUrl}
`
diff --git a/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee b/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
index 1a93887ba..10c726e1a 100644
--- a/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
+++ b/web/app/assets/javascripts/react-components/stores/VideoUploaderStore.js.coffee
@@ -178,6 +178,10 @@ VideoUploaderActions = @VideoUploaderActions
clientUploadCallback: (bytesSent, bytesTotal) ->
logger.debug("bytesSent: #{bytesSent} bytesTotal: #{bytesTotal}")
+
+ # backend will report 0 bytes total sometimes as the upload is failing. just ignore it; we'll get an error message soon
+ return if bytesTotal == 0
+
VideoUploaderStore.bytesSent = Number(bytesSent)
VideoUploaderStore.bytesTotal = Number(bytesTotal)
VideoUploaderStore.triggerState()
From eba43cf228f233b525e81f3e73eb32cb857860fa Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Fri, 9 Oct 2015 06:32:53 -0500
Subject: [PATCH 7/9] * translate enum ids into framerates for start video
recording - VRFS-3631
---
.../stores/RecordingStore.js.jsx.coffee | 13 ++++++++++++-
web/app/assets/javascripts/recordingModel.js | 1 +
2 files changed, 13 insertions(+), 1 deletion(-)
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 d3a57900c..682d8744e 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
@@ -2,6 +2,15 @@ $ = jQuery
context = window
logger = context.JK.logger
+BackendToFrontendFPS = {
+
+ 0: 30,
+ 1: 24,
+ 2: 20,
+ 3: 15,
+ 4: 10
+}
+
@RecordingStore = Reflux.createStore(
{
listenables: @RecordingActions
@@ -25,7 +34,9 @@ logger = context.JK.logger
onStartRecording: (recordVideo, recordChat) ->
- frameRate = context.jamClient.GetCurrentVideoFrameRate() || 30;
+ frameRate = context.jamClient.GetCurrentVideoFrameRate() || 0;
+
+ frameRate = BackendToFrontendFPS[frameRate]
NoVideoRecordActive = 0
WebCamRecordActive = 1
diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js
index 57d9eaf7c..aaa3f7b74 100644
--- a/web/app/assets/javascripts/recordingModel.js
+++ b/web/app/assets/javascripts/recordingModel.js
@@ -96,6 +96,7 @@
// ask the backend to start the session.
var groupedTracks = groupTracksToClient(recording);
+ logger.debug("bridge.StartRecording recordVideo: " + recordVideo + ", recordChat: " + recordChat + ", recordFramerate: " + recordFramerate)
jamClient.StartRecording(recording["id"], groupedTracks, recordVideo, recordChat, recordFramerate);
})
.fail(function(jqXHR) {
From 70b3a2f5de5bcbfd6ae50a54a8dd25c32e5f2b0f Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Fri, 9 Oct 2015 06:37:18 -0500
Subject: [PATCH 8/9] * translate enum ids into framerates for start video
recording - VRFS-3631
---
web/app/assets/javascripts/recordingModel.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/web/app/assets/javascripts/recordingModel.js b/web/app/assets/javascripts/recordingModel.js
index aaa3f7b74..57d9eaf7c 100644
--- a/web/app/assets/javascripts/recordingModel.js
+++ b/web/app/assets/javascripts/recordingModel.js
@@ -96,7 +96,6 @@
// ask the backend to start the session.
var groupedTracks = groupTracksToClient(recording);
- logger.debug("bridge.StartRecording recordVideo: " + recordVideo + ", recordChat: " + recordChat + ", recordFramerate: " + recordFramerate)
jamClient.StartRecording(recording["id"], groupedTracks, recordVideo, recordChat, recordFramerate);
})
.fail(function(jqXHR) {
From 2d54b03a079653aa033d032fc71d50a1aac8a18d Mon Sep 17 00:00:00 2001
From: Seth Call
Date: Fri, 9 Oct 2015 07:20:29 -0500
Subject: [PATCH 9/9] * VRFS-3633 - fix metronome visualization in popup
---
.../javascripts/react-components/MediaControls.js.jsx.coffee | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee b/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee
index ed241cf9e..67e5528f6 100644
--- a/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee
+++ b/web/app/assets/javascripts/react-components/MediaControls.js.jsx.coffee
@@ -78,7 +78,7 @@ mixins.push(Reflux.listenTo(JamTrackStore, 'onJamTrackStateChanged'))
monitorControls: (controls, mediaSummary, jamTrackState) ->
if mediaSummary.mediaOpen || mediaSummary.jamTrack? || jamTrackState?.jamTrack?
- if mediaSummary.jamTrackOpen? || mediaSummary.jamTrack? || jamTrackState?.jamTrack?
+ if mediaSummary.jamTrackOpen || mediaSummary.jamTrack? || jamTrackState?.jamTrack?
controls.startMonitor(PLAYBACK_MONITOR_MODE.JAMTRACK)
else if mediaSummary.backingTrackOpen
controls.startMonitor(PLAYBACK_MONITOR_MODE.MEDIA_FILE)