forked from External/greenlight
		
	Merge pull request #174 from joshua-arts/youtube
Add share button to recordings, and ability to upload to Youtube.
This commit is contained in:
		
							
								
								
									
										6
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -82,3 +82,9 @@ gem 'slack-notifier' | ||||
|  | ||||
| # For landing background image uploading. | ||||
| gem 'paperclip', '~> 4.2' | ||||
|  | ||||
| # For uploading recordings to Youtube. | ||||
| gem 'yt', '~> 0.28.0' | ||||
|  | ||||
| # Simple HTTP client. | ||||
| gem 'faraday' | ||||
|   | ||||
| @@ -229,6 +229,8 @@ GEM | ||||
|       websocket-extensions (>= 0.1.0) | ||||
|     websocket-extensions (0.1.2) | ||||
|     xml-simple (1.1.5) | ||||
|     yt (0.28.5) | ||||
|       activesupport | ||||
|  | ||||
| PLATFORMS | ||||
|   ruby | ||||
| @@ -240,6 +242,7 @@ DEPENDENCIES | ||||
|   byebug | ||||
|   coffee-rails (~> 4.2) | ||||
|   dotenv-rails | ||||
|   faraday | ||||
|   font-awesome-sass | ||||
|   http_accept_language | ||||
|   jbuilder (~> 2.5) | ||||
| @@ -267,6 +270,7 @@ DEPENDENCIES | ||||
|   tzinfo-data | ||||
|   uglifier (>= 1.3.0) | ||||
|   web-console | ||||
|   yt (~> 0.28.0) | ||||
|  | ||||
| RUBY VERSION | ||||
|    ruby 2.3.4p301 | ||||
|   | ||||
| @@ -37,6 +37,18 @@ class @Recordings | ||||
|     COLUMN[c] = i++ | ||||
|  | ||||
|   constructor: -> | ||||
|     recordingsObject = this | ||||
|     canUpload = {} | ||||
|  | ||||
|     # Determine which recordings can be uploaded to Youtube. | ||||
|     $.ajax({ | ||||
|       method: 'GET', | ||||
|       async: false, | ||||
|       url: recordingsObject.getRecordingsURL() + '/can_upload' | ||||
|     }).success((res_data) -> | ||||
|       canUpload = res_data | ||||
|     ) | ||||
|  | ||||
|     # configure the datatable for recordings | ||||
|     this.table = $('#recordings').dataTable({ | ||||
|       data: [], | ||||
| @@ -131,6 +143,14 @@ class @Recordings | ||||
|               trigger = recordingActions.find('.recording-update-trigger') | ||||
|               trigger.removeClass(classes.join(' ')) | ||||
|               trigger.addClass(cls) | ||||
|  | ||||
|               upload_btn = recordingActions.find('.cloud-upload') | ||||
|  | ||||
|               if canUpload[row.id] | ||||
|                 upload_btn.attr('data-popover-body', '.mail_youtube_popover') | ||||
|               else | ||||
|                 upload_btn.attr('data-popover-body', '.mail_popover') | ||||
|  | ||||
|               return recordingActions.html() | ||||
|             return data | ||||
|         } | ||||
| @@ -151,6 +171,22 @@ class @Recordings | ||||
|     options.title = I18n.play_recording | ||||
|     $('#recordings').tooltip(options) | ||||
|  | ||||
|     options.selector = '.youtube-tooltip' | ||||
|     options.title = I18n.upload_youtube | ||||
|     $('#recordings').tooltip(options) | ||||
|  | ||||
|     options.selector = '.upload-tooltip' | ||||
|     options.title = I18n.share | ||||
|     $('#recordings').tooltip(options) | ||||
|  | ||||
|     options.selector = '.mail-tooltip' | ||||
|     options.title = I18n.mail_recording | ||||
|     $('#recordings').tooltip(options) | ||||
|  | ||||
|     options.selector = '.disabled-tooltip' | ||||
|     options.title = I18n.youtube_disabled | ||||
|     $('#recordings').tooltip(options) | ||||
|  | ||||
|     $(document).one "turbolinks:before-cache", => | ||||
|       @getTable().api().clear().draw().destroy() | ||||
|  | ||||
| @@ -229,6 +265,7 @@ class @Recordings | ||||
|   setupActionHandlers: -> | ||||
|     table_api = this.table.api() | ||||
|     recordingsObject = this | ||||
|     selectedUpload = null | ||||
|  | ||||
|     @getTable().on 'click', '.recording-update', (event) -> | ||||
|       btn = $(this) | ||||
| @@ -273,12 +310,73 @@ class @Recordings | ||||
|           showAlert(I18n.recording_deleted, 4000); | ||||
|       ) | ||||
|  | ||||
|     @getTable().on 'click', '.upload-button', (event) -> | ||||
|       btn = $(this) | ||||
|       row = table_api.row($(this).closest('tr')).data() | ||||
|       url = recordingsObject.getRecordingsURL() | ||||
|       id = row.id | ||||
|  | ||||
|       title = $('#video-title').val() | ||||
|       privacy_status = $('input[name=privacy_status]:checked').val() | ||||
|  | ||||
|       if title == '' | ||||
|         title = row.name | ||||
|  | ||||
|       $.ajax({ | ||||
|         method: 'POST', | ||||
|         url: url+'/'+id | ||||
|         data: {video_title: title, privacy_status: privacy_status} | ||||
|         success: () -> | ||||
|           cloud = selectedUpload.find('.cloud-blue') | ||||
|           check = selectedUpload.find('.green-check') | ||||
|           spinner = selectedUpload.find('.load-spinner') | ||||
|  | ||||
|           spinner.hide() | ||||
|           check.show() | ||||
|           setTimeout ( -> | ||||
|             cloud.show() | ||||
|             check.hide() | ||||
|           ), 4000 | ||||
|       }) | ||||
|  | ||||
|       selectedUpload.find('.cloud-blue').hide() | ||||
|       selectedUpload.find('.load-spinner').show() | ||||
|  | ||||
|     @getTable().on 'click', '.mail-recording', (event) -> | ||||
|       btn = $(this) | ||||
|       row = table_api.row($(this).closest('tr')).data() | ||||
|       url = recordingsObject.getRecordingsURL() | ||||
|       id = row.id | ||||
|  | ||||
|       # Take the username from the header. | ||||
|       username = $('#title-header').text().replace('Welcome ', '').trim() | ||||
|  | ||||
|       recording_url = row.playbacks[0].url | ||||
|       webcams_url = getHostName(recording_url) + '/presentation/' + id + '/video/webcams.webm' | ||||
|       subject = username + I18n.recording_mail_subject | ||||
|       body = I18n.recording_mail_body + "\n\n" + recording_url + "\n\n" + I18n.email_footer_1 + "\n" + I18n.email_footer_2 | ||||
|  | ||||
|       mailto = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(body); | ||||
|       window.open(mailto); | ||||
|  | ||||
|     @getTable().on 'click', '.youtube-upload', (event) -> | ||||
|       row = table_api.row($(this).closest('tr')).data() | ||||
|       $('#video-title').attr('value', row.name) | ||||
|  | ||||
|     @getTable().on 'click', '.cloud-upload', (event) -> | ||||
|       selectedUpload = $(this) | ||||
|  | ||||
|     @getTable().on 'draw.dt', (event) -> | ||||
|       $('time[data-time-ago]').timeago(); | ||||
|  | ||||
|   getTable: -> | ||||
|     @table | ||||
|  | ||||
|   getHostName = (url) -> | ||||
|     parser = document.createElement('a'); | ||||
|     parser.href = url; | ||||
|     parser.origin; | ||||
|  | ||||
|   getRecordingsURL: -> | ||||
|     if $(".page-wrapper.rooms").data('main-room') | ||||
|       base_url = Meeting.buildRootURL()+'/'+$('body').data('resource')+'/'+Meeting.getInstance().getAdminId() | ||||
|   | ||||
| @@ -89,3 +89,23 @@ | ||||
|     color: green; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .youtube-red { | ||||
|   color: red; | ||||
| } | ||||
|  | ||||
| .cloud-blue { | ||||
|   color: cornflowerblue; | ||||
| } | ||||
|  | ||||
| .green-check { | ||||
|   color: limegreen; | ||||
| } | ||||
|  | ||||
| .top-buffer { | ||||
|   margin-top: 8px; | ||||
| } | ||||
|  | ||||
| .tooltip-wrapper { | ||||
|   display: inline-block; | ||||
| } | ||||
|   | ||||
| @@ -170,6 +170,41 @@ class BbbController < ApplicationController | ||||
|     render_bbb_response bbb_res | ||||
|   end | ||||
|  | ||||
|   # POST /rooms/:room_id/recordings/:record_id | ||||
|   # POST /rooms/:room_id/:id/recordings/:record_id | ||||
|   def youtube_publish | ||||
|     # If we can't get the client, then they don't have a Youtube account. | ||||
|     begin | ||||
|       client = Yt::Account.new(access_token: current_user.token) | ||||
|       video = client.upload_video(get_webcams_url(params[:record_id]), | ||||
|               title: params[:video_title], | ||||
|               description: t('youtube_description', url: 'https://bigbluebutton.org/'), | ||||
|               privacy_status: params[:privacy_status]) | ||||
|     rescue | ||||
|       # In this case, they don't have a youtube channel connected to their account, so prompt to create one. | ||||
|       redirect_to 'https://m.youtube.com/create_channel' | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   # GET /rooms/:room_id/recordings/can_upload | ||||
|   def can_upload | ||||
|     upload_data = {} | ||||
|     bbb_get_recordings[:recordings].each{ |recording_data| | ||||
|       next if recording_data[:recordID] == "" | ||||
|       # The recording is uploadable if it contains webcam data and they are logged in thorugh Google. | ||||
|       uploadable = Faraday.head(get_webcams_url(recording_data[:recordID])).status == 200 && | ||||
|                    Rails.application.config.omniauth_google && | ||||
|                    current_user.provider == 'google' | ||||
|       upload_data[recording_data[:recordID]] = uploadable | ||||
|     } | ||||
|     render json: upload_data | ||||
|   end | ||||
|  | ||||
|   def get_webcams_url(recording_id) | ||||
|     uri = URI.parse(ENV['BIGBLUEBUTTON_ENDPOINT']) | ||||
|     uri.scheme + '://' + uri.host + '/presentation/' + recording_id + '/video/webcams.webm' | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def load_room! | ||||
|   | ||||
| @@ -141,6 +141,7 @@ module BbbApi | ||||
|     end | ||||
|  | ||||
|     res[:recordings].each do |recording| | ||||
|       next if recording.key?(:error) | ||||
|       pref_preview = {} | ||||
|       recording[:length] = recording[:playback][:format].is_a?(Hash) ? recording[:playback][:format][:length] : recording[:playback][:format].first[:length] | ||||
|       # create a playbacks attribute on recording for playback formats | ||||
|   | ||||
| @@ -26,6 +26,7 @@ class User < ApplicationRecord | ||||
|     user.username = self.send("#{auth_hash['provider']}_username", auth_hash) rescue nil | ||||
|     user.email = self.send("#{auth_hash['provider']}_email", auth_hash) rescue nil | ||||
|     user.name = auth_hash['info']['name'] | ||||
|     user.token = auth_hash['credentials']['token'] rescue nil | ||||
|     user.save! | ||||
|     user | ||||
|   end | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|  | ||||
| <% content_for :title do %> | ||||
|   <div class="title"> | ||||
|     <h2> | ||||
|     <h2 id = 'title-header'> | ||||
|       <% if admin? && !@meeting_running %> | ||||
|         <%= t('admin_room_title', user: @user.name) %> | ||||
|       <% else %> | ||||
|   | ||||
| @@ -55,6 +55,49 @@ | ||||
|       <%= t('no') %> | ||||
|     </button> | ||||
|   </div> | ||||
|   <div class="youtube-popover"> | ||||
|     <form class = "form-inline"> | ||||
|       <div class = "row"> | ||||
|         <div class="col-xs-12"> | ||||
|           <input type="title" class="form-control" id="video-title" placeholder="<%= t('video_title') %>"> | ||||
|         </div> | ||||
|       </div> | ||||
|       <div class = "row top-buffer text-center"> | ||||
|         <div class = "col-xs-10 col-xs-offset-1"> | ||||
|           <form class="privacy_form"> | ||||
|             <label class="radio-inline"><input type="radio" name="privacy_status" value="public" checked><%= t('youtube_privacy_options.public') %> </label> | ||||
|             <label class="radio-inline"><input type="radio" name="privacy_status" value="private"><%= t('youtube_privacy_options.private') %> </label> | ||||
|             <label class="radio-inline"><input type="radio" name="privacy_status" value="unlisted"><%= t('youtube_privacy_options.unlisted') %></label> | ||||
|           </form> | ||||
|         </div> | ||||
|       </div> | ||||
|         <div class="row top-buffer text-center"> | ||||
|           <button type="button" class="btn btn-success upload-button centered" data-dismiss="popover"> | ||||
|             <%= t('upload') %> | ||||
|           </button> | ||||
|         </div> | ||||
|     </form> | ||||
|   </div> | ||||
|   <div class='mail_youtube_popover'> | ||||
|     <button type="button" class="btn btn-default mail-recording mail-tooltip"> | ||||
|       <%= icon('envelope-o') %> | ||||
|     </button> | ||||
|     <button type="button" class="btn btn-default has-popover youtube-upload youtube-tooltip" | ||||
|           data-placement="top" data-popover-body=".youtube-popover" | ||||
|           data-popover-title="<%= t('upload_to_youtube') %>" > | ||||
|       <div class = 'youtube-red'> <%= icon('youtube-play') %> </div> | ||||
|     </button> | ||||
|   </div> | ||||
|   <div class='mail_popover'> | ||||
|     <button type="button" class="btn btn-default mail-recording mail-tooltip fa-2x"> | ||||
|       <%= icon('envelope-o') %> | ||||
|     </button> | ||||
|     <div class="tooltip-wrapper disabled disabled-tooltip"> | ||||
|       <button type="button" class="btn btn-default" disabled> | ||||
|           <%= icon('youtube-play') %> | ||||
|       </button> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div class="recording-visibility-popover"> | ||||
|     <button type="button" class="btn btn-default btn-success recording-update" data-visibility="published"> | ||||
|       <%= t('client.published') %> | ||||
| @@ -72,6 +115,12 @@ | ||||
|             data-popover-title="<%= t('change_recording_visibility') %>"> | ||||
|       <%= icon('eye') %> | ||||
|     </button> | ||||
|     <button type="button" class="btn btn-default has-popover cloud-upload upload-tooltip" | ||||
|             data-placement="top"> | ||||
|       <div class = 'cloud-blue'> <%= icon('cloud-upload') %> </div> | ||||
|       <div class = 'green-check' hidden> <%= icon('check') %> </div> | ||||
|       <div class = 'load-spinner fa-spin' hidden> <%= icon('spinner') %> </div> | ||||
|     </button> | ||||
|     <a tabindex="0" role="button" class="btn btn-default has-popover delete-tooltip" | ||||
|        data-placement="top" data-popover-body=".delete-popover-body" | ||||
|        data-popover-title="<%= t('are_you_sure') %>"> | ||||
|   | ||||
| @@ -7,5 +7,5 @@ Rails.application.config.omniauth_twitter = ENV['TWITTER_ID'].present? | ||||
| Rails.application.config.middleware.use OmniAuth::Builder do | ||||
|   provider :twitter, ENV['TWITTER_ID'], ENV['TWITTER_SECRET'] | ||||
|   provider :google_oauth2, ENV['GOOGLE_OAUTH2_ID'], ENV['GOOGLE_OAUTH2_SECRET'], | ||||
|     scope: ['profile', 'email'], access_type: 'online', name: 'google' | ||||
|     scope: ['profile', 'email', 'youtube', 'youtube.upload'], access_type: 'online', name: 'google' | ||||
| end | ||||
|   | ||||
							
								
								
									
										21
									
								
								config/initializers/youtube.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								config/initializers/youtube.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| # BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. | ||||
| # | ||||
| # Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below). | ||||
| # | ||||
| # This program is free software; you can redistribute it and/or modify it under the | ||||
| # terms of the GNU Lesser General Public License as published by the Free Software | ||||
| # Foundation; either version 3.0 of the License, or (at your option) any later | ||||
| # version. | ||||
| # | ||||
| # BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY | ||||
| # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||||
| # PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU Lesser General Public License along | ||||
| # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| Yt.configure do |config| | ||||
|   config.log_level = :debug | ||||
|   config.client_id = ENV['GOOGLE_OAUTH2_ID'] | ||||
|   config.client_secret = ENV['GOOGLE_OAUTH2_SECRET'] | ||||
| end | ||||
| @@ -46,8 +46,11 @@ en-US: | ||||
|   client: | ||||
|     are_you_sure: Are you sure? | ||||
|     change_visibility: Change visibility | ||||
|     delete_recording: Delete recording | ||||
|     delete_recording: Delete | ||||
|     email_footer_1: This e-mail is auto-generated by your friendly neighbourhood BigBlueButton server. Do no reply to this e-mail. | ||||
|     email_footer_2: BigBlueButton is an open source web conferencing system. For more information on BigBlueButton and it's amazing capabilities, see https://bigbluebutton.org/ . | ||||
|     enter_name: Enter a name to join | ||||
|     mail_recording: Mail | ||||
|     meeting_ended: Meeting was ended | ||||
|     meeting_started: Meeting was started | ||||
|     no_recordings: No Recordings | ||||
| @@ -58,13 +61,18 @@ en-US: | ||||
|     recording_created: A recording was created | ||||
|     recording_deleted: Recording was deleted | ||||
|     recording_published: Recording was published | ||||
|     recording_mail_subject: " has invited you to view a recording." | ||||
|     recording_mail_body: "To view the recording, follow the link below:" | ||||
|     recording_unlisted: Recording was unlisted | ||||
|     recording_unpublished: Recording was unpublished | ||||
|     share: Share | ||||
|     unpublish_recording: Hide recording | ||||
|     unlisted: Unlisted | ||||
|     unpublished: No one | ||||
|     upload_youtube: Youtube | ||||
|     user_waiting_body: "%{user} is waiting to join %{meeting}!" | ||||
|     user_waiting_title: A user is waiting | ||||
|     youtube_disabled: Recording did not contain any video. | ||||
|   copied: Copied | ||||
|   copy_error: Use Ctrl-c to copy | ||||
|   create_your_session: Create your own meeting | ||||
| @@ -155,11 +163,18 @@ en-US: | ||||
|       phrase1: This is a test email sent to %{email} | ||||
|   thumbnails: Thumbnails | ||||
|   upload: Upload | ||||
|   upload_to_youtube: Upload to Youtube | ||||
|   url_copy_explanation: Copy this URL to invite others to the meeting | ||||
|   user_person_room: "%{name} personal room" | ||||
|   video: Video | ||||
|   video_title: Video title | ||||
|   visibility: Visibility | ||||
|   wait_for_mod_msg: Looks like you're the first one here... | ||||
|   wait_for_mod_explanation: You will automatically join when the meeting starts | ||||
|   watch: Watch | ||||
|   'yes': 'Yes' | ||||
|   youtube_description: This recording was recorded with BigBlueButton. For more information check out %{url}. | ||||
|   youtube_privacy_options: | ||||
|     public: Public | ||||
|     private: Private | ||||
|     unlisted: Unlisted | ||||
|   | ||||
| @@ -38,6 +38,8 @@ Rails.application.routes.draw do | ||||
|     scope '/:room_id', :constraints => {:room_id => disallow_slash} do | ||||
|       # recording routes for updating, deleting and viewing recordings | ||||
|       get '/(:id)/recordings', to: 'bbb#recordings', defaults: {id: nil, format: 'json'}, :constraints => {:id => disallow_slash} | ||||
|       get '/(:id)/recordings/can_upload', to: 'bbb#can_upload', defaults: {id: nil, format: 'json'}, :constraints => {:id => disallow_slash} | ||||
|       post '/(:id)/recordings/:record_id', to: 'bbb#youtube_publish', defaults: {id: nil, format: 'json'}, :constraints => {:id => disallow_slash} | ||||
|       patch '/(:id)/recordings/:record_id', to: 'bbb#update_recordings', defaults: {id: nil, format: 'json'}, :constraints => {:id => disallow_slash} | ||||
|       delete '/(:id)/recordings/:record_id', to: 'bbb#delete_recordings', defaults: {id: nil, format: 'json'}, :constraints => {:id => disallow_slash} | ||||
|  | ||||
|   | ||||
							
								
								
									
										5
									
								
								db/migrate/20170518190442_add_token_to_users.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								db/migrate/20170518190442_add_token_to_users.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| class AddTokenToUsers < ActiveRecord::Migration[5.0] | ||||
|   def change | ||||
|     add_column :users, :token, :string | ||||
|   end | ||||
| end | ||||
| @@ -10,7 +10,7 @@ | ||||
| # | ||||
| # It's strongly recommended that you check this file into your version control system. | ||||
|  | ||||
| ActiveRecord::Schema.define(version: 20170510175654) do | ||||
| ActiveRecord::Schema.define(version: 20170518190442) do | ||||
|  | ||||
|   create_table "users", force: :cascade do |t| | ||||
|     t.string   "provider",                null: false | ||||
| @@ -25,6 +25,7 @@ ActiveRecord::Schema.define(version: 20170510175654) do | ||||
|     t.string   "background_content_type" | ||||
|     t.integer  "background_file_size" | ||||
|     t.datetime "background_updated_at" | ||||
|     t.string   "token" | ||||
|     t.index ["email"], name: "index_users_on_email", unique: true | ||||
|     t.index ["encrypted_id"], name: "index_users_on_encrypted_id", unique: true | ||||
|     t.index ["provider", "uid"], name: "index_users_on_provider_and_uid", unique: true | ||||
|   | ||||
		Reference in New Issue
	
	Block a user