* VRFS-3512 - upload videos to youtube
This commit is contained in:
parent
4290d449ed
commit
276442a9b4
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
ALTER TABLE recordings ADD video BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
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;
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
10
web/Gemfile
10
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'
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
var $isPublic = null;
|
||||
var $saveBtn = null;
|
||||
var $deleteBtn = null;
|
||||
|
||||
var $videoField = null;
|
||||
var updating = false;
|
||||
var deleting = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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 = $('<a class="video-thumbnail" href="https://www.youtube.com/watch?v=' + feed.external_video_id + '" rel="external">' +
|
||||
'<img class="video" src="//img.youtube.com/vi/' + feed.external_video_id + '/hqdefault.jpg" />' +
|
||||
'<img class="play" src="/assets/content/icon_youtube_play.png" /></a>')
|
||||
$videoContainer.append($embed).addClass('no-embed')
|
||||
$videoWrapper.removeClass('hidden')
|
||||
$embed.click(function() {
|
||||
context.JK.popExternalLink($(this).attr('href'))
|
||||
return false;
|
||||
})
|
||||
}
|
||||
else {
|
||||
var $embed = $('<iframe src="//www.youtube.com/embed/' + feed.external_video_id + '" frameborder="0" allowfullscreen="allowfullscreen" />')
|
||||
$videoContainer.append($embed)
|
||||
$videoWrapper.removeClass('hidden')
|
||||
}
|
||||
|
||||
if(feed.owner_id == context.JK.currentUserId) {
|
||||
var $deleteVideoLink = $('<a href="#" class="delete-video">delete video</a>')
|
||||
$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);
|
||||
|
|
|
|||
|
|
@ -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){
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ if accessOpener
|
|||
<span className="field">
|
||||
<input type="checkbox" name="dont_show" /><label htmlFor="dont_show">Don't show this again</label>
|
||||
</span>
|
||||
<a className="button-orange close-link" onClick={this.close}>CLOSE</a>
|
||||
<a className="button-orange close-link" onClick={this.close}>CLOSE</a>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = `<div><a className="button-grey close-btn" onClick={this.onCloseRequested}>CANCEL</a> <a className="button-orange close-btn" onClick={this.onDeleteVideo}>DELETE</a></div>`
|
||||
instructions = `<p>Save space and delete your video from your computer?</p>`
|
||||
else if @state.recording.video_url?
|
||||
action = `<a className="button-orange close-btn" onClick={this.onCloseRequested}>CLOSE</a>`
|
||||
instructions = `<p>Your video has been uploaded previously. <a className="video-url" onClick={this.watchVideo} href={this.state.recording.video_url}>{this.state.uploadState.videoUrl}</a></p>`
|
||||
else if @state.uploadState.errorReason
|
||||
action = `<a className="button-orange upload-btn" onClick={this.onUploadRequested}>UPLOAD</a>`
|
||||
instructions = `<p>The upload failed. You can try again. (error reason={this.state.uploadState.errorReason})</p>`
|
||||
else if @state.uploadState.done
|
||||
action = `<a className="button-orange close-btn" onClick={this.onNextToDeletion}>OK</a>`
|
||||
instructions = `<p>Your video has been uploaded successfully. <a className="video-url" onClick={this.watchVideo} href={this.state.uploadState.videoUrl}>{this.state.uploadState.videoUrl}</a></p>`
|
||||
else if @state.uploadState.paused
|
||||
action = `<a className="button-orange resume-btn" onClick={this.onResumeRequested}>RESUME</a>`
|
||||
instructions = `<p>Press the RESUME button at any time to continue uploading your video.</p>`
|
||||
else if @state.uploadState.uploading
|
||||
action = `<div><a className="button-grey cancel-btn" onClick={this.onCancelRequested}>CANCEL</a> <a className="button-grey pause-btn" onClick={this.onPauseRequested}>PAUSE</a></div>`
|
||||
width = ((@state.uploadState.bytesSent / @state.uploadState.bytesTotal) * 100)
|
||||
progressWidth = width.toString() + "%";
|
||||
progressStyle = {width:progressWidth}
|
||||
rounded = Math.round(width)
|
||||
instructions = `<p>Press the PAUSE button at any time to pause the upload of your video, or CANCEL to end this upload completely.
|
||||
<div className="progress-bar" style={progressStyle}><div className="percentage-progress">{rounded}%</div></div>
|
||||
</p>`
|
||||
else if @state.auth == false
|
||||
action = `<input className="google_login_button" type='image' src="/assets/google_signin.png" height="30px" onClick={this.startGoogleLogin}/>`
|
||||
instructions = `<p>To upload this recording to YouTube, you must give JamKazam the necessary access to your YouTube account by clicking the button below.</p>`
|
||||
else if @state.auth?
|
||||
action = `<a className="button-orange upload-btn" onClick={this.onUploadRequested}>UPLOAD</a>`
|
||||
instructions = `<p>Press the UPLOAD button to start uploading your video to YouTube. When the upload is done, a link to your video will appear.</p>`
|
||||
else
|
||||
name = null
|
||||
action = null
|
||||
else
|
||||
name = null
|
||||
action = null
|
||||
|
||||
`<div className="video-uploader">
|
||||
<h3>Upload Video<br/>"{name}"</h3>
|
||||
<h3></h3>
|
||||
{instructions}
|
||||
<div className="control-holder">
|
||||
{action}
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
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()
|
||||
})
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
context = window
|
||||
|
||||
@VideoUploaderActions = Reflux.createActions({
|
||||
uploadVideo: {},
|
||||
pause: {},
|
||||
resume: {},
|
||||
cancel: {},
|
||||
newVideo: {},
|
||||
showUploader: {},
|
||||
uploaderClosed: {}
|
||||
delete: {}
|
||||
})
|
||||
|
|
@ -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: () ->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
@import "client/common";
|
||||
|
||||
#deleteVideoConfirmDialog {
|
||||
|
||||
min-height: 180px;
|
||||
width: 400px;
|
||||
|
||||
.buttons {
|
||||
float:right;
|
||||
clear:both;
|
||||
margin-top:20px;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -192,7 +192,7 @@
|
|||
@include border_box_sizing;
|
||||
@include vertical-align-column;
|
||||
|
||||
.export {
|
||||
.export, .open-video {
|
||||
float:right;
|
||||
margin-right:10px;
|
||||
font-size:12px;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) : {}
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
object @user
|
||||
|
||||
child :user_authorizations => :authorizations do
|
||||
attributes :uid, :provider, :token, :secret
|
||||
end
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -43,3 +43,4 @@
|
|||
= render 'dialogs/genreSelectorDialog'
|
||||
= render 'dialogs/recordingSelectorDialog'
|
||||
= render 'dialogs/soundCloudPlayerDialog'
|
||||
= render 'dialogs/deleteVideoConfirmDialog'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
- provide(:page_name, 'video-upload popup')
|
||||
- provide(:title, 'Video Upload')
|
||||
= react_component 'PopupVideoUploader', {}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<% provide(:description, 'Finished authorization') %>
|
||||
|
||||
This window will close momentarily and you can then continue.
|
||||
|
||||
<script>
|
||||
window.opener._oauth_callback();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@
|
|||
= '{% } %}'
|
||||
= '{% }) %}'
|
||||
|
||||
.video-wrapper.hidden
|
||||
.video-container
|
||||
|
||||
|
||||
%br{:clear => "all"}/
|
||||
%br/
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue