proctoring 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +245 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/100ms_manifest.js +1 -0
  6. data/app/assets/config/knights_watch_manifest.js +4 -0
  7. data/app/assets/config/kurento_manifest.js +1 -0
  8. data/app/assets/config/proctoring_manifest.js +3 -0
  9. data/app/assets/config/videojs_manifest.js +2 -0
  10. data/app/assets/images/proctoring/poster.png +0 -0
  11. data/app/assets/javascripts/100ms/examine.js +27 -0
  12. data/app/assets/javascripts/100ms/hundred_ms.js +143 -0
  13. data/app/assets/javascripts/100ms/join_proctor_room.js +17 -0
  14. data/app/assets/javascripts/100ms/proctor.js +152 -0
  15. data/app/assets/javascripts/kurento/LiveVideoUsingSignalingServer.js +344 -0
  16. data/app/assets/javascripts/kurento/VideoPlayer.js +63 -0
  17. data/app/assets/javascripts/kurento/VideoRecording.js +286 -0
  18. data/app/assets/javascripts/kurento/VideoRecordingUsingSignalingServer.js +224 -0
  19. data/app/assets/javascripts/kurento/co.js +299 -0
  20. data/app/assets/javascripts/kurento/kurento-utils.js +4418 -0
  21. data/app/assets/javascripts/proctoring/stream_channel.js +66 -0
  22. data/app/assets/javascripts/proctoring/stream_room.js +131 -0
  23. data/app/assets/javascripts/proctoring/video_recorder.js +172 -0
  24. data/app/assets/javascripts/videojs/videojs-playlist-ui.js +516 -0
  25. data/app/assets/javascripts/videojs/videojs-playlist.js +909 -0
  26. data/app/assets/stylesheets/proctoring/application.css +15 -0
  27. data/app/assets/stylesheets/proctoring/video_player_100ms.css +34 -0
  28. data/app/assets/stylesheets/proctoring/video_streamings.css +49 -0
  29. data/app/assets/stylesheets/scaffold.css +80 -0
  30. data/app/assets/stylesheets/videojs/videojs-playlist-ui.css +1 -0
  31. data/app/controllers/proctoring/api/v1/authentication_controller.rb +31 -0
  32. data/app/controllers/proctoring/api/v1/hundred_ms/services_controller.rb +24 -0
  33. data/app/controllers/proctoring/application_controller.rb +23 -0
  34. data/app/controllers/proctoring/video_streamings_controller.rb +108 -0
  35. data/app/helpers/proctoring/application_helper.rb +4 -0
  36. data/app/helpers/proctoring/hundred_ms_service_helper.rb +90 -0
  37. data/app/helpers/proctoring/tokens_helper.rb +55 -0
  38. data/app/helpers/proctoring/video_streamings_helper.rb +4 -0
  39. data/app/jobs/proctoring/application_job.rb +4 -0
  40. data/app/mailers/proctoring/application_mailer.rb +6 -0
  41. data/app/models/proctoring/application_record.rb +5 -0
  42. data/app/models/proctoring/video_streaming.rb +54 -0
  43. data/app/models/proctoring/video_streaming_room.rb +29 -0
  44. data/app/views/layouts/proctoring/application.html.erb +15 -0
  45. data/app/views/proctoring/video_player/live_video_proctoring.html.erb +84 -0
  46. data/app/views/proctoring/video_player/live_video_proctoring_100ms.html.erb +48 -0
  47. data/app/views/proctoring/video_player/video_player.html.erb +40 -0
  48. data/app/views/proctoring/video_streamings/_form.html.erb +27 -0
  49. data/app/views/proctoring/video_streamings/_list.html.erb +27 -0
  50. data/app/views/proctoring/video_streamings/_record_video_from_client.html.erb +8 -0
  51. data/app/views/proctoring/video_streamings/_socket_rtc_scripts.html.erb +5 -0
  52. data/app/views/proctoring/video_streamings/_stream_video.html.erb +8 -0
  53. data/app/views/proctoring/video_streamings/_video_recording.html.erb +3 -0
  54. data/app/views/proctoring/video_streamings/_video_recording100ms.html.erb +4 -0
  55. data/app/views/proctoring/video_streamings/distribute_channel_to_rooms.html.erb +39 -0
  56. data/app/views/proctoring/video_streamings/edit.html.erb +6 -0
  57. data/app/views/proctoring/video_streamings/event.html.erb +9 -0
  58. data/app/views/proctoring/video_streamings/index.html.erb +1 -0
  59. data/app/views/proctoring/video_streamings/new.html.erb +5 -0
  60. data/app/views/proctoring/video_streamings/show.html.erb +19 -0
  61. data/app/views/proctoring/video_streamings/stream_channel.html.erb +1 -0
  62. data/app/views/proctoring/video_streamings/stream_room.html.erb +1 -0
  63. data/config/routes.rb +24 -0
  64. data/db/migrate/20200526061313_create_proctoring_video_streamings.rb +15 -0
  65. data/db/migrate/20200527045158_create_proctoring_video_streaming_rooms.rb +13 -0
  66. data/lib/proctoring/engine.rb +41 -0
  67. data/lib/proctoring/version.rb +3 -0
  68. data/lib/proctoring.rb +5 -0
  69. data/lib/tasks/proctoring_tasks.rake +4 -0
  70. metadata +158 -0
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,34 @@
1
+ #proctoring-videos {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ }
5
+ #proctoring-videos .video-container{
6
+ border-width: 5px;
7
+ }
8
+ #proctoring-videos .video-container video {
9
+ width: 320px;
10
+ height: 240px;
11
+ }
12
+
13
+ .border-danger .status-online,
14
+ .border-success .status-offline {
15
+ display: none;
16
+ }
17
+
18
+ .loader {
19
+ border: 16px solid #f3f3f3; /* Light grey */
20
+ border-top: 16px solid #3498db; /* Blue */
21
+ border-radius: 50%;
22
+ width: 120px;
23
+ height: 120px;
24
+ animation: spin 2s linear infinite;
25
+ position: absolute;
26
+ top: 30%;
27
+ left: 45%;
28
+ transform: translate(-50%,-50%);
29
+ }
30
+
31
+ @keyframes spin {
32
+ 0% { transform: rotate(0deg); }
33
+ 100% { transform: rotate(360deg); }
34
+ }
@@ -0,0 +1,49 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
5
+ /* #video-container video{ */
6
+ /* border: 2px solid #ddd; */
7
+ /* min-width: 640px; */
8
+ /* min-height: 480px; */
9
+ /* } */
10
+
11
+ .video-div {
12
+ border-radius: 5px;
13
+ border: 1px solid black;
14
+ margin: 0 5px;
15
+ width: 320px;
16
+ overflow: hidden;
17
+ display: inline-block;
18
+ }
19
+
20
+ .video-div video {
21
+ width: 320px;
22
+ height: 240px;
23
+ }
24
+
25
+ .video-div h2 {
26
+ border-top: 5px solid #080808;
27
+ padding: 5px 10px;
28
+ margin: 0;
29
+ margin-top: -4px;
30
+ overflow: hidden;
31
+ }
32
+
33
+ #videos-container {
34
+ text-align: center;
35
+ }
36
+
37
+ table {
38
+ border-collapse: collapse;
39
+ border-spacing: 0;
40
+ width: 100%;
41
+ border: 1px solid #ddd;
42
+ }
43
+
44
+ th, td {
45
+ text-align: left;
46
+ padding: 8px;
47
+ }
48
+
49
+ tr:nth-child(even){background-color: #f2f2f2}
@@ -0,0 +1,80 @@
1
+ body {
2
+ background-color: #fff;
3
+ color: #333;
4
+ margin: 33px;
5
+ }
6
+
7
+ body, p, ol, ul, td {
8
+ font-family: verdana, arial, helvetica, sans-serif;
9
+ font-size: 13px;
10
+ line-height: 18px;
11
+ }
12
+
13
+ pre {
14
+ background-color: #eee;
15
+ padding: 10px;
16
+ font-size: 11px;
17
+ }
18
+
19
+ a {
20
+ color: #000;
21
+ }
22
+
23
+ a:visited {
24
+ color: #666;
25
+ }
26
+
27
+ a:hover {
28
+ color: #fff;
29
+ background-color: #000;
30
+ }
31
+
32
+ th {
33
+ padding-bottom: 5px;
34
+ }
35
+
36
+ td {
37
+ padding: 0 5px 7px;
38
+ }
39
+
40
+ div.field,
41
+ div.actions {
42
+ margin-bottom: 10px;
43
+ }
44
+
45
+ #notice {
46
+ color: green;
47
+ }
48
+
49
+ .field_with_errors {
50
+ padding: 2px;
51
+ background-color: red;
52
+ display: table;
53
+ }
54
+
55
+ #error_explanation {
56
+ width: 450px;
57
+ border: 2px solid red;
58
+ padding: 7px 7px 0;
59
+ margin-bottom: 20px;
60
+ background-color: #f0f0f0;
61
+ }
62
+
63
+ #error_explanation h2 {
64
+ text-align: left;
65
+ font-weight: bold;
66
+ padding: 5px 5px 5px 15px;
67
+ font-size: 12px;
68
+ margin: -7px -7px 0;
69
+ background-color: #c00;
70
+ color: #fff;
71
+ }
72
+
73
+ #error_explanation ul li {
74
+ font-size: 12px;
75
+ list-style: square;
76
+ }
77
+
78
+ label {
79
+ display: block;
80
+ }
@@ -0,0 +1 @@
1
+ .vjs-playlist{padding:0;background-color:#1a1a1a;color:#fff;list-style-type:none}.vjs-playlist img{display:block;height:auto;width:auto}.vjs-playlist .vjs-playlist-item-list{position:relative;margin:0;padding:0;list-style:none}.vjs-playlist .vjs-playlist-item{position:relative;cursor:pointer;overflow:hidden}.vjs-playlist .vjs-playlist-thumbnail-placeholder{background:#303030}.vjs-playlist .vjs-playlist-now-playing-text{display:none;position:absolute;top:0;left:0;padding-left:2px;margin:.8rem}.vjs-playlist .vjs-playlist-duration{position:absolute;top:.5rem;left:.5rem;padding:2px 5px 3px;margin-left:2px;background-color:rgba(26,26,26,0.8)}.vjs-playlist .vjs-playlist-title-container{position:absolute;bottom:0;box-sizing:border-box;width:100%;padding:.5rem .8rem;text-shadow:1px 1px 2px black, -1px 1px 2px black, 1px -1px 2px black, -1px -1px 2px black}.vjs-playlist .vjs-playlist-name{display:block;max-height:2.5em;padding:0 0 4px 2px;font-style:normal;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;line-height:20px}.vjs-playlist .vjs-playlist-description{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;display:block;font-size:14px;padding:0 0 0 2px}.vjs-playlist .vjs-up-next-text{display:none;padding:.1rem 2px;font-size:.8em;text-transform:uppercase}.vjs-playlist .vjs-up-next .vjs-up-next-text{display:block}.vjs-playlist .vjs-selected{background-color:#141a21}.vjs-playlist .vjs-selected img{opacity:.2}.vjs-playlist .vjs-selected .vjs-playlist-duration{display:none}.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text{display:block}.vjs-playlist .vjs-selected .vjs-playlist-title-container{text-shadow:none}.vjs-playlist-vertical{overflow-x:hidden;overflow-y:auto}.vjs-playlist-vertical img{width:100%;min-height:54px}.vjs-playlist-vertical .vjs-playlist-item{margin-bottom:5px}.vjs-playlist-vertical .vjs-playlist-thumbnail{display:block;width:100%}.vjs-playlist-vertical .vjs-playlist-thumbnail-placeholder{height:100px}.vjs-playlist-horizontal{overflow-x:auto;overflow-y:hidden}.vjs-playlist-horizontal img{min-width:100px;height:100%}.vjs-playlist-horizontal .vjs-playlist-item-list{height:100%;white-space:nowrap}.vjs-playlist-horizontal .vjs-playlist-item{display:inline-block;height:100%;margin-right:5px}.vjs-playlist-horizontal .vjs-playlist-thumbnail{display:block;height:100%}.vjs-playlist-horizontal .vjs-playlist-thumbnail-placeholder{height:100%;width:180px}.vjs-playlist.vjs-ad-playing{overflow:hidden}.vjs-playlist.vjs-ad-playing.vjs-csspointerevents{pointer-events:none;overflow:auto}.vjs-playlist.vjs-ad-playing.vjs-csspointerevents .vjs-playlist-ad-overlay{pointer-events:auto}.vjs-playlist.vjs-ad-playing .vjs-playlist-ad-overlay{display:block;position:absolute;top:0;left:0;width:100%;height:100%;background-color:#1a1a1a;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";background-color:rgba(0,0,0,0.5)}.vjs-playlist{font-size:14px}.vjs-playlist .vjs-playlist-description{height:28px;line-height:21px}.vjs-mouse.vjs-playlist{font-size:15px}.vjs-mouse.vjs-playlist .vjs-playlist-description{height:30px;line-height:23px}@media (min-width: 600px){.vjs-mouse.vjs-playlist{font-size:17px}.vjs-mouse.vjs-playlist .vjs-playlist-description{height:34px;line-height:26px}.vjs-playlist .vjs-playlist-name{line-height:22px}}@media (max-width: 520px){.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text,.vjs-playlist .vjs-up-next .vjs-up-next-text{display:none}.vjs-mouse.vjs-playlist .vjs-selected .vjs-playlist-now-playing-text,.vjs-mouse.vjs-playlist .vjs-up-next .vjs-up-next-text{display:none}}@media (min-width: 521px){.vjs-playlist img{min-height:85px}}@media (max-width: 750px){.vjs-playlist .vjs-playlist-duration{display:none}}
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proctoring::Api::V1
4
+ class AuthenticationController < Proctoring::ApplicationController
5
+ include Proctoring::TokensHelper
6
+ include Proctoring::HundredMsServiceHelper
7
+ before_action :authenticate_app_token
8
+
9
+ def create
10
+ event_id = params[:event_id]
11
+ user_id = params[:user_id]
12
+ role = params[:role]
13
+ max_people_allowed = params[:max_people_allowed]
14
+
15
+ room_id = room_already_exists?(event_id, user_id)
16
+
17
+ room_id ||= room_exists_with_space?(event_id, user_id, role, max_people_allowed)
18
+
19
+ if room_id.blank?
20
+ room_id = generate_room({ event_id: event_id })['id']
21
+ add_room_to_the_cache(room_id, user_id, role, event_id)
22
+ end
23
+
24
+ authentication_params = { user_id: user_id, role: role, room_id: room_id }
25
+ authentication_token = generate_authentication_token(authentication_params)
26
+ render json: { success: true, authentication_token: authentication_token }
27
+ rescue StandardError => e
28
+ render json: { success: false, error: e.message }, status: :internal_server_error
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Proctoring::Api::V1
4
+ module HundredMs
5
+ class ServicesController < Proctoring::ApplicationController
6
+ skip_before_action :verify_authenticity_token
7
+ before_action :authenticate_api_token
8
+
9
+ def create
10
+ hundred_ms_response = JSON.parse(request.raw_post)
11
+
12
+ case hundred_ms_response['type']
13
+ when 'recording.success'
14
+ data = hundred_ms_response['data']
15
+ room_name = data['room_name']
16
+ invitation_code = room_name.split('-').first
17
+ ProctoringStorageHelper.initiate_processing(invitation_code)
18
+ end
19
+
20
+ render json: { success: true }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module Proctoring
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ def authenticate_app_token
6
+ token_valid? request.headers['Token']
7
+ end
8
+
9
+ def authenticate_api_token
10
+ return if request.headers['Api-Secret'] == Proctoring.hundred_ms_webhook_secret
11
+
12
+ render json: { success: false, error: 'You are not an authorized person.' }, status: :unauthorized
13
+ end
14
+
15
+ private
16
+
17
+ def token_valid?(token)
18
+ JWT.decode token, Proctoring.hundred_ms_auth_secret, true, { algorithm: 'HS256' }
19
+ rescue StandardError => _e
20
+ render json: { success: false, error: 'You are not an authorized person.' }, status: :unauthorized
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,108 @@
1
+ require_dependency "proctoring/application_controller"
2
+
3
+ module Proctoring
4
+ class VideoStreamingsController < ApplicationController
5
+ before_action :set_video_streaming, only: [:show, :edit, :update, :destroy, :upload_video]
6
+
7
+ # GET /video_streamings
8
+ def index
9
+ @video_streamings = VideoStreaming.where(status: :active).limit(15)
10
+ end
11
+
12
+ # GET /video_streamings/1
13
+ def show
14
+ end
15
+
16
+ # GET /video_streamings/new
17
+ def new
18
+ @video_streaming = VideoStreaming.new
19
+ end
20
+
21
+ # GET /video_streamings/1/edit
22
+ def edit
23
+ end
24
+
25
+ # POST /video_streamings
26
+ def create
27
+ @video_streaming = VideoStreaming.new(video_streaming_params)
28
+
29
+ if @video_streaming.save
30
+ redirect_to @video_streaming, notice: 'Video streaming was successfully created.'
31
+ else
32
+ render :new
33
+ end
34
+ end
35
+
36
+ # PATCH/PUT /video_streamings/1
37
+ def update
38
+ if @video_streaming.update(video_streaming_params)
39
+ redirect_to @video_streaming, notice: 'Video streaming was successfully updated.'
40
+ else
41
+ render :edit
42
+ end
43
+ end
44
+
45
+ # PATCH/PUT /video_streamings/1/upload_video
46
+ def upload_video
47
+ video_streaming = video_streaming_params
48
+ # return unless session[:user_id] != video_streaming[:user_id]
49
+ # video_streaming[:videos] = @video_streaming.all_attached_videos_sign_ids + video_streaming[:videos]
50
+ # p video_streaming
51
+ if @video_streaming.update(video_streaming_params)
52
+ render json: {}, status: :ok
53
+ else
54
+ render json: {}, status: 422
55
+ end
56
+ end
57
+
58
+ # DELETE /video_streamings/1
59
+ def destroy
60
+ @video_streaming.stopped!
61
+ redirect_to video_streamings_url, notice: 'Video streaming was successfully destroyed.'
62
+ end
63
+
64
+ def event
65
+ @video_streamings = VideoStreaming.by_event(params[:id])
66
+ end
67
+
68
+ def user_channel
69
+ user_id = params[:user_id]
70
+ event_id = params[:event_id]
71
+ video_streaming = VideoStreaming.open_join_channel(user_id, event_id)
72
+ render json: { id: video_streaming.id, channel: video_streaming.channel, socketURL: Proctoring.media_server_url }
73
+ end
74
+
75
+ def stream_channel
76
+ @media_server_url = Proctoring.media_server_url
77
+ @video_streaming = VideoStreaming.find_by(channel: params[:id])
78
+ @channels = [@video_streaming.channel]
79
+ end
80
+
81
+ def stream_room
82
+ @media_server_url = Proctoring.media_server_url
83
+ @video_streaming_room = VideoStreamingRoom.find_by(room: params[:id])
84
+ end
85
+
86
+ def distribute_channel_to_rooms
87
+ @no_of_users_in_channel = proctor_user_params[:no_of_users_in_channel].to_i
88
+ @event_id = proctor_user_params[:event_id]
89
+ VideoStreaming.setup_rooms(@event_id, @no_of_users_in_channel)
90
+ @all_rooms = VideoStreamingRoom.where(event_id: @event_id, total_users: @no_of_users_in_channel).active
91
+ end
92
+
93
+ private
94
+ # Use callbacks to share common setup or constraints between actions.
95
+ def set_video_streaming
96
+ @video_streaming = VideoStreaming.find(params[:id])
97
+ end
98
+
99
+ # Only allow a trusted parameter "white list" through.
100
+ def video_streaming_params
101
+ params.require(:video_streaming).permit(:channel, :user_id, :event_id, videos: [], images: [])
102
+ end
103
+
104
+ def proctor_user_params
105
+ params.require(:video_streaming).permit(:no_of_users_in_channel, :event_id)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,4 @@
1
+ module Proctoring
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ module Proctoring
5
+ module HundredMsServiceHelper
6
+ include Proctoring::TokensHelper
7
+ def generate_room(params)
8
+ uri = get_api_url('create_room')
9
+ uri = URI.parse(uri)
10
+ http = Net::HTTP.new(uri.host, uri.port)
11
+ http.use_ssl = true
12
+ request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json' })
13
+ request['Authorization'] = "Bearer #{generate_management_token}"
14
+ body = get_create_room_request_parameters(params[:event_id])
15
+ request.body = body.to_json
16
+ response = http.request(request)
17
+ JSON.parse(response.body)
18
+ end
19
+
20
+ def room_already_exists?(event_id, user_id)
21
+ rooms = fetch_rooms_for_a_event(event_id)
22
+ return if rooms.blank?
23
+
24
+ rooms.each do |room_id, candidates|
25
+ candidates.each do |candidate_hash|
26
+ return room_id if candidate_hash[:user_id] == user_id
27
+ end
28
+ end; nil
29
+ end
30
+
31
+ def room_exists_with_space?(event_id, user_id, role, max_people_allowed)
32
+ rooms = fetch_rooms_for_a_event(event_id)
33
+ return if rooms.blank?
34
+
35
+ rooms.each do |room_id, candidates|
36
+ roles = candidates.map { |candidate_hash| candidate_hash[:role] }
37
+ next if roles.count(role) >= max_people_allowed.to_i
38
+
39
+ candidates << { user_id: user_id, role: role }
40
+ rooms[room_id] = candidates
41
+ Rails.cache.write("100ms_room_details_#{event_id}", rooms, expires_in: 2.hours)
42
+ return room_id
43
+ end; nil
44
+ end
45
+
46
+ def add_room_to_the_cache(room_id, user_id, role, event_id)
47
+ candidates = [{ user_id: user_id, role: role }]
48
+ rooms = fetch_rooms_for_a_event(event_id) || {}
49
+ rooms[room_id] = candidates
50
+ Rails.cache.write("100ms_room_details_#{event_id}", rooms, expires_in: 2.hours)
51
+ end
52
+
53
+ private
54
+
55
+ def get_api_url(service_name)
56
+ case service_name
57
+ when 'create_room'
58
+ 'https://api.100ms.live/v2/rooms'
59
+ end
60
+ end
61
+
62
+ def get_create_room_request_parameters(event_id)
63
+ {
64
+ name: "#{event_id}-#{SecureRandom.uuid}",
65
+ description: "Room for the event with id - #{event_id}",
66
+ template_id: Proctoring.hundred_ms_template_id,
67
+ recording_info: {
68
+ enabled: true,
69
+ upload_info: {
70
+ type: 's3',
71
+ location: Proctoring.hundred_ms_s3_bucket,
72
+ prefix: event_id,
73
+ options: {
74
+ region: Proctoring.hundred_ms_s3_region
75
+ },
76
+ credentials: {
77
+ key: Proctoring.hundred_ms_s3_access_key,
78
+ secret: Proctoring.hundred_ms_s3_access_secret
79
+ }
80
+ }
81
+ },
82
+ region: 'in'
83
+ }
84
+ end
85
+
86
+ def fetch_rooms_for_a_event(event_id)
87
+ Rails.cache.read("100ms_room_details_#{event_id}")
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ require 'jwt'
3
+ require 'securerandom'
4
+
5
+ module Proctoring
6
+ module TokensHelper
7
+ def generate_authentication_token(params)
8
+ now = Time.now
9
+ exp = now + 1.day
10
+ payload = {
11
+ access_key: Proctoring.hundred_ms_app_access_key,
12
+ room_id: params[:room_id],
13
+ user_id: params[:user_id],
14
+ role: params[:role],
15
+ type: 'app',
16
+ jti: SecureRandom.uuid,
17
+ version: 2,
18
+ iat: now.to_i,
19
+ nbf: now.to_i,
20
+ exp: exp.to_i
21
+ }
22
+ JWT.encode(payload, Proctoring.hundred_ms_app_secret, 'HS256')
23
+ end
24
+
25
+ def generate_management_token
26
+ now = Time.now
27
+ exp = now + 1.day
28
+ payload = {
29
+ access_key: Proctoring.hundred_ms_app_access_key,
30
+ type: 'management',
31
+ version: 2,
32
+ jti: SecureRandom.uuid,
33
+ iat: now.to_i,
34
+ nbf: now.to_i,
35
+ exp: exp.to_i
36
+ }
37
+ JWT.encode(payload, Proctoring.hundred_ms_app_secret, 'HS256')
38
+ end
39
+
40
+ def self.encode_authentication_token
41
+ now = Time.now
42
+ exp = now + 10.minutes
43
+ payload = {
44
+ app_name: Proctoring.app_name,
45
+ type: 'application',
46
+ jti: SecureRandom.uuid,
47
+ version: 2,
48
+ iat: now.to_i,
49
+ nbf: now.to_i,
50
+ exp: exp.to_i
51
+ }
52
+ JWT.encode payload, Proctoring.hundred_ms_auth_secret, 'HS256'
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,4 @@
1
+ module Proctoring
2
+ module VideoStreamingsHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Proctoring
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Proctoring
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Proctoring
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,54 @@
1
+ module Proctoring
2
+ class VideoStreaming < ApplicationRecord
3
+ validates :channel, presence: true
4
+ validates :user_id, presence: true
5
+ validates :event_id, presence: true
6
+ validates_uniqueness_of :user_id, scope: [:event_id]
7
+ has_many_attached :images
8
+ has_many_attached :videos
9
+
10
+ enum status: { active: 0, stopped: 1 }
11
+
12
+ before_validation :ensure_channel_has_a_value
13
+
14
+ scope :by_event, ->(event_id) { where(event_id: event_id) }
15
+
16
+ def all_attached_videos_sign_ids
17
+ videos.map(&:signed_id)
18
+ end
19
+
20
+ def self.setup_rooms(event_id, no_of_users_in_channel)
21
+ @channels = VideoStreaming.by_event(event_id).active.pluck(:channel)
22
+ video_streaming_room = VideoStreamingRoom.where(event_id: event_id, total_users: no_of_users_in_channel).active
23
+ if (video_streaming_room.length.positive?)
24
+ channels_in_room = video_streaming_room.pluck(:channels)&.flatten
25
+ return if @channels == channels_in_room
26
+ video_streaming_room.map(&:closed!)
27
+ end
28
+
29
+ channels_by_room = @channels.each_slice(no_of_users_in_channel).to_a if @channels.length.positive? && no_of_users_in_channel.positive?
30
+ VideoStreaming.transaction do
31
+ channels_by_room.each do |channels|
32
+ VideoStreamingRoom.create(event_id: event_id, total_users: no_of_users_in_channel, channels: channels)
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.open_join_channel(user_id, event_id)
38
+ video_streaming = VideoStreaming.find_by(event_id: event_id, user_id: user_id)
39
+ unless video_streaming
40
+ video_streaming = VideoStreaming.create(event_id: event_id, user_id: user_id)
41
+ end
42
+ video_streaming
43
+ end
44
+
45
+ private
46
+
47
+ def ensure_channel_has_a_value
48
+ if channel.nil?
49
+ self.channel = SecureRandom.uuid
50
+ self.status = :active
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ module Proctoring
2
+ class VideoStreamingRoom < ApplicationRecord
3
+ validates :room, presence: true
4
+ validates :event_id, presence: true
5
+ validates :channels, presence: true
6
+ validates :total_users, presence: true
7
+
8
+ # validate :check_total_users_equals_channels
9
+
10
+ enum status: { active: 0, closed: 1 }
11
+
12
+ before_validation :ensure_room_has_a_value
13
+
14
+ private
15
+
16
+ def ensure_room_has_a_value
17
+ if room.nil?
18
+ self.room = SecureRandom.uuid
19
+ self.status = :active
20
+ end
21
+ end
22
+
23
+ # def check_total_users_equals_channels
24
+ # if self.total_users != self.channels&.length
25
+ # errors.add(:total_users, "Total users are not equal to channels in room.")
26
+ # end
27
+ # end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Proctoring</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "proctoring/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>