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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +245 -0
- data/Rakefile +32 -0
- data/app/assets/config/100ms_manifest.js +1 -0
- data/app/assets/config/knights_watch_manifest.js +4 -0
- data/app/assets/config/kurento_manifest.js +1 -0
- data/app/assets/config/proctoring_manifest.js +3 -0
- data/app/assets/config/videojs_manifest.js +2 -0
- data/app/assets/images/proctoring/poster.png +0 -0
- data/app/assets/javascripts/100ms/examine.js +27 -0
- data/app/assets/javascripts/100ms/hundred_ms.js +143 -0
- data/app/assets/javascripts/100ms/join_proctor_room.js +17 -0
- data/app/assets/javascripts/100ms/proctor.js +152 -0
- data/app/assets/javascripts/kurento/LiveVideoUsingSignalingServer.js +344 -0
- data/app/assets/javascripts/kurento/VideoPlayer.js +63 -0
- data/app/assets/javascripts/kurento/VideoRecording.js +286 -0
- data/app/assets/javascripts/kurento/VideoRecordingUsingSignalingServer.js +224 -0
- data/app/assets/javascripts/kurento/co.js +299 -0
- data/app/assets/javascripts/kurento/kurento-utils.js +4418 -0
- data/app/assets/javascripts/proctoring/stream_channel.js +66 -0
- data/app/assets/javascripts/proctoring/stream_room.js +131 -0
- data/app/assets/javascripts/proctoring/video_recorder.js +172 -0
- data/app/assets/javascripts/videojs/videojs-playlist-ui.js +516 -0
- data/app/assets/javascripts/videojs/videojs-playlist.js +909 -0
- data/app/assets/stylesheets/proctoring/application.css +15 -0
- data/app/assets/stylesheets/proctoring/video_player_100ms.css +34 -0
- data/app/assets/stylesheets/proctoring/video_streamings.css +49 -0
- data/app/assets/stylesheets/scaffold.css +80 -0
- data/app/assets/stylesheets/videojs/videojs-playlist-ui.css +1 -0
- data/app/controllers/proctoring/api/v1/authentication_controller.rb +31 -0
- data/app/controllers/proctoring/api/v1/hundred_ms/services_controller.rb +24 -0
- data/app/controllers/proctoring/application_controller.rb +23 -0
- data/app/controllers/proctoring/video_streamings_controller.rb +108 -0
- data/app/helpers/proctoring/application_helper.rb +4 -0
- data/app/helpers/proctoring/hundred_ms_service_helper.rb +90 -0
- data/app/helpers/proctoring/tokens_helper.rb +55 -0
- data/app/helpers/proctoring/video_streamings_helper.rb +4 -0
- data/app/jobs/proctoring/application_job.rb +4 -0
- data/app/mailers/proctoring/application_mailer.rb +6 -0
- data/app/models/proctoring/application_record.rb +5 -0
- data/app/models/proctoring/video_streaming.rb +54 -0
- data/app/models/proctoring/video_streaming_room.rb +29 -0
- data/app/views/layouts/proctoring/application.html.erb +15 -0
- data/app/views/proctoring/video_player/live_video_proctoring.html.erb +84 -0
- data/app/views/proctoring/video_player/live_video_proctoring_100ms.html.erb +48 -0
- data/app/views/proctoring/video_player/video_player.html.erb +40 -0
- data/app/views/proctoring/video_streamings/_form.html.erb +27 -0
- data/app/views/proctoring/video_streamings/_list.html.erb +27 -0
- data/app/views/proctoring/video_streamings/_record_video_from_client.html.erb +8 -0
- data/app/views/proctoring/video_streamings/_socket_rtc_scripts.html.erb +5 -0
- data/app/views/proctoring/video_streamings/_stream_video.html.erb +8 -0
- data/app/views/proctoring/video_streamings/_video_recording.html.erb +3 -0
- data/app/views/proctoring/video_streamings/_video_recording100ms.html.erb +4 -0
- data/app/views/proctoring/video_streamings/distribute_channel_to_rooms.html.erb +39 -0
- data/app/views/proctoring/video_streamings/edit.html.erb +6 -0
- data/app/views/proctoring/video_streamings/event.html.erb +9 -0
- data/app/views/proctoring/video_streamings/index.html.erb +1 -0
- data/app/views/proctoring/video_streamings/new.html.erb +5 -0
- data/app/views/proctoring/video_streamings/show.html.erb +19 -0
- data/app/views/proctoring/video_streamings/stream_channel.html.erb +1 -0
- data/app/views/proctoring/video_streamings/stream_room.html.erb +1 -0
- data/config/routes.rb +24 -0
- data/db/migrate/20200526061313_create_proctoring_video_streamings.rb +15 -0
- data/db/migrate/20200527045158_create_proctoring_video_streaming_rooms.rb +13 -0
- data/lib/proctoring/engine.rb +41 -0
- data/lib/proctoring/version.rb +3 -0
- data/lib/proctoring.rb +5 -0
- data/lib/tasks/proctoring_tasks.rake +4 -0
- 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,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,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
|