diff --git a/app/channels/room_channel.rb b/app/channels/room_channel.rb index d135811b2..769115282 100644 --- a/app/channels/room_channel.rb +++ b/app/channels/room_channel.rb @@ -2,6 +2,7 @@ class RoomChannel < ApplicationCable::Channel def subscribed @room = find_room stream_for @room + @room.update(participant_count: (@room.participant_count + 1)) broadcast_to @room, { type: 'ping', from: params[:client_id] } end @@ -10,6 +11,12 @@ def unsubscribed find_room, target: "medium_#{current_client.id}" ) + @room.update(participant_count: (@room.participant_count + -1)) + Turbo::StreamsChannel.broadcast_replace_to( + target: 'room-stats', + partial: 'rooms/participants', + locals: { room: find_room } + ) end def greet(data) @@ -20,11 +27,17 @@ def greet(data) partial: 'media/medium', locals: { client_id: data['from'], user: user} ) + Turbo::StreamsChannel.broadcast_replace_to( + data['to'], + target: 'room-stats', + partial: 'rooms/participants', + locals: { room: find_room } + ) end private def find_room - Room.new(id: params[:id]) + Room.find_by(external_room_id: params[:id]) end end diff --git a/app/channels/signaling_channel.rb b/app/channels/signaling_channel.rb index fb7b66ed0..f06c2c6a4 100644 --- a/app/channels/signaling_channel.rb +++ b/app/channels/signaling_channel.rb @@ -1,9 +1,9 @@ class SignalingChannel < ApplicationCable::Channel def subscribed - stream_for Room.new(id: params[:id]) + stream_for Room.find_by(external_room_id: params[:id]) end def signal(data) - broadcast_to(Room.new(id: params[:id]), data) + broadcast_to(Room.find_by(external_room_id: params[:id]), data) end end diff --git a/app/controllers/comfy/admin/rooms_controller.rb b/app/controllers/comfy/admin/rooms_controller.rb index 3ce186ef3..5f0151e05 100644 --- a/app/controllers/comfy/admin/rooms_controller.rb +++ b/app/controllers/comfy/admin/rooms_controller.rb @@ -1,9 +1,23 @@ class Comfy::Admin::RoomsController < Comfy::Admin::Cms::BaseController def new + @room = Room.new render 'rooms/new' end def create - redirect_to room_path(SecureRandom.uuid) + external_room_id = SecureRandom.uuid + Room.create!(room_params.merge(external_room_id: external_room_id, user_id: current_user.id)) + redirect_to room_path(external_room_id) + end + + private + + def room_params + params.require(:room).permit( + :name, + :active, + :require_authentication, + :owner_broadcast_only, + ) end end diff --git a/app/controllers/rooms_controller.rb b/app/controllers/rooms_controller.rb index 86a38d938..b34ec1d34 100644 --- a/app/controllers/rooms_controller.rb +++ b/app/controllers/rooms_controller.rb @@ -1,10 +1,31 @@ class RoomsController < ApplicationController + before_action :load_room + before_action :check_authentication, only: [:show] + def show + @is_room_owner = false @client = Client.new(id: SecureRandom.uuid) cookies.encrypted[:client_id] = @client.id - @room = Room.new(id: params[:id]) + if current_user + @is_room_owner = current_user.id == @room.user_id + end @user = current_user @visit = current_visit end + + private + + def check_authentication + if @room.require_authentication + unless current_user + flash.alert = "you need to sign in first!" + redirect_to new_user_session_path + end + end + end + + def load_room + @room = Room.find_by(external_room_id: params[:id]) + end end diff --git a/app/javascript/controllers/room_controller.js b/app/javascript/controllers/room_controller.js index e54d9e458..2f1fcb362 100644 --- a/app/javascript/controllers/room_controller.js +++ b/app/javascript/controllers/room_controller.js @@ -28,11 +28,16 @@ export default class RoomController extends Controller { async enter () { try { - const constraints = { audio: true, video: true } - this.client.stream = await navigator.mediaDevices.getUserMedia(constraints) + let video = $('#allowVideo').is(':checked') + let audio = $('#allowAudio').is(':checked') + const constraints = { audio: audio, video: video } + this.client.constraints = constraints + if (video || audio) { + this.client.stream = await navigator.mediaDevices.getUserMedia(constraints) + } this.localMediumTarget.srcObject = this.client.stream this.localMediumTarget.muted = true // Keep muted on Firefox - this.enterTarget.hidden = true + this.enterTarget.hidden = true // Hide enter controls this.subscription.start() this.signaller.start() @@ -95,7 +100,9 @@ export default class RoomController extends Controller { } startStreamingTo (otherClient) { - this.client.streamTo(otherClient) + if (this.client.constraints.video || this.client.constraints.audio) { + this.client.streamTo(otherClient) + } } startStreamingFrom (id, { track, streams: [stream] }) { diff --git a/app/javascript/models/client.js b/app/javascript/models/client.js index a9e36de63..24be1569f 100644 --- a/app/javascript/models/client.js +++ b/app/javascript/models/client.js @@ -2,6 +2,7 @@ export default class Client { constructor (id) { this.callbacks = {} this.id = id + this.constraints = {} } get peerConnection () { diff --git a/app/models/room.rb b/app/models/room.rb index 6177c2cd4..578882930 100644 --- a/app/models/room.rb +++ b/app/models/room.rb @@ -1,10 +1,4 @@ -class Room - attr_reader :id - - def initialize(id:) - @id = id - end - +class Room < ApplicationRecord def to_param id end diff --git a/app/views/rooms/_controls.html.erb b/app/views/rooms/_controls.html.erb new file mode 100644 index 000000000..d3056b160 --- /dev/null +++ b/app/views/rooms/_controls.html.erb @@ -0,0 +1,4 @@ +<%= label_tag "enable video?" %> +<%= check_box_tag :allowVideo, true %> +<%= label_tag "enable audio?" %> +<%= check_box_tag :allowAudio, true %> \ No newline at end of file diff --git a/app/views/rooms/_participants.html.erb b/app/views/rooms/_participants.html.erb new file mode 100644 index 000000000..414841615 --- /dev/null +++ b/app/views/rooms/_participants.html.erb @@ -0,0 +1 @@ +

Participants: <%= room.participant_count %>

\ No newline at end of file diff --git a/app/views/rooms/new.html.erb b/app/views/rooms/new.html.erb index b3ac18085..d5df21439 100644 --- a/app/views/rooms/new.html.erb +++ b/app/views/rooms/new.html.erb @@ -5,6 +5,16 @@

Early access, feature under active development

-<%= form_with url: rooms_path do |form| %> - <%= form.submit 'Create Room' %> +<%= form_for @room, url: rooms_path do |form| %> +
+ <%= form.label "Room owner broadcast (spectators only)" %> + <%= form.check_box :owner_broadcast_only, checked: false %> +
+
+ <%= form.label "Require authentication (no visitors)" %> + <%= form.check_box :require_authentication, checked: false %> +
+
+ <%= form.submit 'Create Room', class: 'btn btn-primary' %> +
<% end %> \ No newline at end of file diff --git a/app/views/rooms/show.html.erb b/app/views/rooms/show.html.erb index ea20d467f..9aaa3bcdb 100644 --- a/app/views/rooms/show.html.erb +++ b/app/views/rooms/show.html.erb @@ -1,12 +1,21 @@ <%= turbo_stream_from @room %> <%= turbo_stream_from @client.id %> -

Room ID: <%= @room.id %>

+

+ <% if @room.require_authentication %> + Secure Room ID: <%= @room.external_room_id %> + <% else %> + Public Room ID: <%= @room.external_room_id %> + <% end %> +

+
+ <%= render 'rooms/participants', room: @room %> +
@@ -20,7 +29,18 @@
<% end %>
- <%= button_tag 'Enter', data: { room_target: 'enter', action: 'room#enter' } %> +
+ <% if !@room.owner_broadcast_only %> + <%= render "controls" %> + <% else %> + <% if @is_room_owner %> + <%= render "controls" %> + <% else %> +

The owner of the room does not allow participants to share audio/video

+ <% end %> + <% end %> + <%= button_tag 'Enter', data: { action: 'room#enter' }, class: 'btn btn-primary d-block' %> +
diff --git a/db/migrate/20230925182202_add_rooms_table.rb b/db/migrate/20230925182202_add_rooms_table.rb new file mode 100755 index 000000000..b79c4b0ca --- /dev/null +++ b/db/migrate/20230925182202_add_rooms_table.rb @@ -0,0 +1,16 @@ +class AddRoomsTable < ActiveRecord::Migration[6.1] + def change + create_table :rooms do |t| + t.string :name + t.string :external_room_id, null: false + t.boolean :active, default: true + t.references :user, null: true, foreign_key: true + t.boolean :require_authentication, default: true + t.boolean :owner_broadcast_only, default: true + t.integer :participant_count, default: 0 + + t.timestamps + end + add_index :rooms, :external_room_id, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 4c2e5e965..aeed590c2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_09_13_160600) do +ActiveRecord::Schema.define(version: 2023_09_25_182202) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -478,6 +478,20 @@ t.index ["api_resource_id"], name: "index_non_primitive_properties_on_api_resource_id" end + create_table "rooms", force: :cascade do |t| + t.string "name" + t.string "external_room_id", null: false + t.boolean "active", default: true + t.bigint "user_id" + t.boolean "require_authentication", default: true + t.boolean "owner_broadcast_only", default: true + t.integer "participant_count", default: 0 + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["external_room_id"], name: "index_rooms_on_external_room_id", unique: true + t.index ["user_id"], name: "index_rooms_on_user_id" + end + create_table "subdomain_requests", force: :cascade do |t| t.string "subdomain_name" t.string "email" @@ -621,5 +635,6 @@ add_foreign_key "messages", "message_threads" add_foreign_key "non_primitive_properties", "api_namespaces" add_foreign_key "non_primitive_properties", "api_resources" + add_foreign_key "rooms", "users" add_foreign_key "webhook_verification_methods", "external_api_clients" end