Merge pull request #17972 from gustavotrott/graphql-media-sharing

refactor (graphql-server): Add externalVideo and screenshare to Graphql
This commit is contained in:
Gustavo Trott 2023-05-23 09:12:13 -03:00 committed by GitHub
commit 1d870a294c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 299 additions and 7 deletions

View File

@ -4,7 +4,8 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x.{ requestBroadcastStop }
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x.requestBroadcastStop
import org.bigbluebutton.core.db.ExternalVideoDAO
trait StartExternalVideoPubMsgHdlr extends RightsManagementTrait {
this: ExternalVideoApp2x =>
@ -39,6 +40,7 @@ trait StartExternalVideoPubMsgHdlr extends RightsManagementTrait {
requestBroadcastStop(bus.outGW, liveMeeting)
ExternalVideoModel.setURL(liveMeeting.externalVideoModel, msg.body.externalVideoUrl)
ExternalVideoDAO.insert(liveMeeting.props.meetingProp.intId, msg.body.externalVideoUrl)
broadcastEvent(msg)
}
}

View File

@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.externalvideo
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.ExternalVideoDAO
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core2.message.senders.MsgBuilder
@ -19,6 +20,8 @@ trait StopExternalVideoPubMsgHdlr extends RightsManagementTrait {
} else {
ExternalVideoModel.clear(liveMeeting.externalVideoModel)
ExternalVideoDAO.updateStopped(liveMeeting.props.meetingProp.intId)
//broadcastEvent
val msgEvent = MsgBuilder.buildStopExternalVideoEvtMsg(liveMeeting.props.meetingProp.intId, msg.header.userId)
bus.outGW.send(msgEvent)

View File

@ -3,7 +3,8 @@ package org.bigbluebutton.core.apps.externalvideo
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting }
import org.bigbluebutton.core.db.ExternalVideoDAO
import org.bigbluebutton.core.running.LiveMeeting
trait UpdateExternalVideoPubMsgHdlr extends RightsManagementTrait {
@ -24,6 +25,7 @@ trait UpdateExternalVideoPubMsgHdlr extends RightsManagementTrait {
val reason = "You need to be the presenter to update external video"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
ExternalVideoDAO.update(liveMeeting.props.meetingProp.intId, msg.body.status, msg.body.rate, msg.body.time, msg.body.state)
broadcastEvent(msg)
}
}

View File

@ -1,8 +1,9 @@
package org.bigbluebutton.core.apps.screenshare
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ ScreenshareModel, ExternalVideoModel }
import org.bigbluebutton.core.apps.{ ExternalVideoModel, ScreenshareModel }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.ScreenshareDAO
import org.bigbluebutton.core.running.LiveMeeting
trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr {
@ -48,6 +49,8 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr {
log.info("START broadcast ALLOWED when isBroadcastingRTMP=false")
ScreenshareDAO.insert(liveMeeting.props.meetingProp.intId, liveMeeting.screenshareModel)
// Notify viewers in the meeting that there's an rtmp stream to view
val msgEvent = broadcastEvent(msg.body.voiceConf, msg.body.screenshareConf, msg.body.stream,
msg.body.vidWidth, msg.body.vidHeight, msg.body.timestamp, msg.body.hasAudio)

View File

@ -2,10 +2,12 @@ package org.bigbluebutton.core.apps.screenshare
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.ScreenshareModel
import org.bigbluebutton.core.apps.ScreenshareModel.getRTMPBroadcastingUrl
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x.{ broadcastStopped }
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x.broadcastStopped
import org.bigbluebutton.core.db.ScreenshareDAO
trait ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsgHdlr {
this: ScreenshareApp2x =>
@ -15,6 +17,8 @@ trait ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsgHdlr {
ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel) + " URL:" +
ScreenshareModel.getRTMPBroadcastingUrl(liveMeeting.screenshareModel))
ScreenshareDAO.updateStopped(liveMeeting.props.meetingProp.intId, getRTMPBroadcastingUrl(liveMeeting.screenshareModel))
broadcastStopped(bus.outGW, liveMeeting)
}
}

View File

@ -0,0 +1,86 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.util.RandomStringGenerator
import slick.jdbc.PostgresProfile.api._
import slick.lifted.ProvenShape
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class ExternalVideoDbModel(
externalVideoId: String,
meetingId: String,
externalVideoUrl: String,
startedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis()),
stoppedAt: Option[java.sql.Timestamp],
lastEventAt: Option[java.sql.Timestamp],
lastEventDesc: String,
playerRate: Double,
playerTime: Double,
playerState: Int
)
class ExternalVideoDbTableDef(tag: Tag) extends Table[ExternalVideoDbModel](tag, "external_video") {
val externalVideoId = column[String]("externalVideoId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val externalVideoUrl = column[String]("externalVideoUrl")
val startedAt = column[java.sql.Timestamp]("startedAt")
val stoppedAt = column[Option[java.sql.Timestamp]]("stoppedAt")
val lastEventAt = column[Option[java.sql.Timestamp]]("lastEventAt")
val lastEventDesc = column[String]("lastEventDesc")
val playerRate = column[Double]("playerRate")
val playerTime = column[Double]("playerTime")
val playerState = column[Int]("playerState")
override def * : ProvenShape[ExternalVideoDbModel] = (externalVideoId, meetingId, externalVideoUrl, startedAt, stoppedAt, lastEventAt, lastEventDesc, playerRate, playerTime, playerState) <> (ExternalVideoDbModel.tupled, ExternalVideoDbModel.unapply)
}
object ExternalVideoDAO {
def insert(meetingId: String, externalVideoUrl: String) = {
DatabaseConnection.db.run(
TableQuery[ExternalVideoDbTableDef].forceInsert(
ExternalVideoDbModel(
externalVideoId = System.currentTimeMillis() + "-" + RandomStringGenerator.randomAlphanumericString(8),
meetingId = meetingId,
externalVideoUrl = externalVideoUrl,
startedAt = new java.sql.Timestamp(System.currentTimeMillis()),
stoppedAt = None,
lastEventAt = None,
lastEventDesc = "",
playerRate = 0,
playerTime = 0,
playerState = 0,
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in ExternalVideo table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting ExternalVideo: $e")
}
}
def update(meetingId: String, status: String, rate: Double, time: Double, state: Int) = {
DatabaseConnection.db.run(
TableQuery[ExternalVideoDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.stoppedAt.isEmpty)
.map(ev => (ev.lastEventDesc, ev.playerRate, ev.playerTime, ev.playerState, ev.lastEventAt))
.update((status, rate, time, state, Some(new java.sql.Timestamp(System.currentTimeMillis()))))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on ExternalVideo table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating ExternalVideo: $e")
}
}
def updateStopped(meetingId: String) = {
DatabaseConnection.db.run(
TableQuery[ExternalVideoDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.stoppedAt.isEmpty)
.map(ev => ev.stoppedAt)
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated stoppedAt on ExternalVideo table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating stoppedAt on ExternalVideo: $e")
}
}
}

View File

@ -0,0 +1,76 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.apps.ScreenshareModel
import org.bigbluebutton.core.apps.ScreenshareModel.{ getHasAudio, getRTMPBroadcastingUrl, getScreenshareConf, getScreenshareVideoHeight, getScreenshareVideoWidth, getVoiceConf }
import org.bigbluebutton.core.util.RandomStringGenerator
import slick.jdbc.PostgresProfile.api._
import slick.lifted.ProvenShape
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class ScreenshareDbModel(
screenshareId: String,
meetingId: String,
voiceConf: String,
screenshareConf: String,
stream: String,
vidWidth: Int,
vidHeight: Int,
startedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis()),
stoppedAt: Option[java.sql.Timestamp],
hasAudio: Boolean
)
class ScreenshareDbTableDef(tag: Tag) extends Table[ScreenshareDbModel](tag, "screenshare") {
val screenshareId = column[String]("screenshareId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val voiceConf = column[String]("voiceConf")
val screenshareConf = column[String]("screenshareConf")
val stream = column[String]("stream")
val vidWidth = column[Int]("vidWidth")
val vidHeight = column[Int]("vidHeight")
val startedAt = column[java.sql.Timestamp]("startedAt")
val stoppedAt = column[Option[java.sql.Timestamp]]("stoppedAt")
val hasAudio = column[Boolean]("hasAudio")
override def * : ProvenShape[ScreenshareDbModel] = (screenshareId, meetingId, voiceConf, screenshareConf, stream, vidWidth, vidHeight, startedAt, stoppedAt, hasAudio) <> (ScreenshareDbModel.tupled, ScreenshareDbModel.unapply)
}
object ScreenshareDAO {
def insert(meetingId: String, screenshareModel: ScreenshareModel) = {
DatabaseConnection.db.run(
TableQuery[ScreenshareDbTableDef].forceInsert(
ScreenshareDbModel(
screenshareId = System.currentTimeMillis() + "-" + RandomStringGenerator.randomAlphanumericString(8),
meetingId = meetingId,
voiceConf = getVoiceConf(screenshareModel),
screenshareConf = getScreenshareConf(screenshareModel),
stream = getRTMPBroadcastingUrl(screenshareModel),
vidWidth = getScreenshareVideoWidth(screenshareModel),
vidHeight = getScreenshareVideoHeight(screenshareModel),
startedAt = new java.sql.Timestamp(System.currentTimeMillis()),
stoppedAt = None,
hasAudio = getHasAudio(screenshareModel)
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Screenshare table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Screenshare: $e")
}
}
def updateStopped(meetingId: String, stream: String) = {
DatabaseConnection.db.run(
TableQuery[ScreenshareDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.stream === stream)
.filter(_.stoppedAt.isEmpty)
.map(ev => ev.stoppedAt)
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated stoppedAt on Screenshare table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating stoppedAt on Screenshare: $e")
}
}
}

View File

@ -61,6 +61,10 @@ drop view if exists "v_poll";
drop table if exists "poll_response";
drop table if exists "poll_option";
drop table if exists "poll";
drop view if exists "v_external_video";
drop table if exists "external_video";
drop view if exists "v_screenshare";
drop table if exists "screenshare";
DROP FUNCTION IF EXISTS "update_user_presenter_trigger_func";
@ -851,7 +855,7 @@ CREATE INDEX "idx_poll_response_pollId" ON "poll_response"("pollId");
CREATE INDEX "idx_poll_response_userId" ON "poll_response"("userId");
CREATE INDEX "idx_poll_response_pollId_userId" ON "poll_response"("pollId", "userId");
CREATE OR REPLACE VIEW v_poll_response AS
CREATE OR REPLACE VIEW "v_poll_response" AS
SELECT
poll."meetingId",
poll."pollId",
@ -869,7 +873,7 @@ LEFT JOIN poll_response r ON r."pollId" = poll."pollId" AND o."optionId" = r."op
GROUP BY poll."pollId", o."optionId", o."optionDesc"
ORDER BY poll."pollId";
CREATE VIEW v_poll_user AS
CREATE VIEW "v_poll_user" AS
SELECT
poll."meetingId",
poll."pollId",
@ -886,10 +890,54 @@ LEFT JOIN poll_response r ON r."pollId" = poll."pollId" AND r."userId" = u."user
LEFT JOIN poll_option o ON o."pollId" = r."pollId" AND o."optionId" = r."optionId"
GROUP BY poll."pollId", u."userId", u.name ;
CREATE VIEW v_poll AS SELECT * FROM poll;
CREATE VIEW "v_poll" AS SELECT * FROM "poll";
CREATE VIEW v_poll_option AS
SELECT poll."meetingId", poll."pollId", o."optionId", o."optionDesc"
FROM poll_option o
JOIN poll using("pollId")
WHERE poll."type" != 'R-';
--------------------------------
----External video
create table "external_video"(
"externalVideoId" varchar(100) primary key,
"meetingId" varchar(100) REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
"externalVideoUrl" varchar(500),
"startedAt" timestamp,
"stoppedAt" timestamp,
"lastEventAt" timestamp,
"lastEventDesc" varchar(50),
"playerRate" numeric,
"playerTime" numeric,
"playerState" integer
);
create index "external_video_meetingId_current" on "external_video"("meetingId") WHERE "stoppedAt" IS NULL;
CREATE VIEW "v_external_video" AS
SELECT * FROM "external_video"
WHERE "stoppedAt" IS NULL;
--------------------------------
----Screenshare
create table "screenshare"(
"screenshareId" varchar(50) primary key,
"meetingId" varchar(100) REFERENCES "meeting"("meetingId") ON DELETE CASCADE,
"voiceConf" varchar(50),
"screenshareConf" varchar(50),
"stream" varchar(100),
"vidWidth" integer,
"vidHeight" integer,
"startedAt" timestamp,
"stoppedAt" timestamp,
"hasAudio" boolean
);
create index "screenshare_meetingId" on "screenshare"("meetingId");
create index "screenshare_meetingId_current" on "screenshare"("meetingId") WHERE "stoppedAt" IS NULL;
CREATE VIEW "v_screenshare" AS
SELECT * FROM "screenshare"
WHERE "stoppedAt" IS NULL;

View File

@ -11,6 +11,15 @@ object_relationships:
remote_table:
name: v_meeting_breakoutPolicies
schema: public
- name: externalVideo
using:
manual_configuration:
column_mapping:
meetingId: meetingId
insertion_order: null
remote_table:
name: v_external_video
schema: public
- name: lockSettings
using:
manual_configuration:
@ -29,6 +38,15 @@ object_relationships:
remote_table:
name: v_meeting_recordingPolicies
schema: public
- name: screenshare
using:
manual_configuration:
column_mapping:
meetingId: meetingId
insertion_order: null
remote_table:
name: v_screenshare
schema: public
- name: usersPolicies
using:
manual_configuration:

View File

@ -0,0 +1,24 @@
table:
name: v_external_video
schema: public
configuration:
column_config: {}
custom_column_names: {}
custom_name: external_video
custom_root_fields: {}
select_permissions:
- role: bbb_client
permission:
columns:
- externalVideoId
- externalVideoUrl
- lastEventAt
- lastEventDesc
- playerRate
- playerState
- playerTime
- startedAt
- stoppedAt
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -0,0 +1,24 @@
table:
name: v_screenshare
schema: public
configuration:
column_config: {}
custom_column_names: {}
custom_name: screenshare
custom_root_fields: {}
select_permissions:
- role: bbb_client
permission:
columns:
- hasAudio
- screenshareConf
- screenshareId
- startedAt
- stoppedAt
- stream
- vidHeight
- vidWidth
- voiceConf
filter:
meetingId:
_eq: X-Hasura-MeetingId

View File

@ -4,6 +4,7 @@
- "!include public_v_chat.yaml"
- "!include public_v_chat_message_private.yaml"
- "!include public_v_chat_message_public.yaml"
- "!include public_v_external_video.yaml"
- "!include public_v_meeting_breakoutPolicies.yaml"
- "!include public_v_meeting_group.yaml"
- "!include public_v_meeting_lockSettings.yaml"
@ -19,6 +20,7 @@
- "!include public_v_pres_annotation_history_curr.yaml"
- "!include public_v_pres_page_cursor.yaml"
- "!include public_v_pres_page_writers.yaml"
- "!include public_v_screenshare.yaml"
- "!include public_v_user.yaml"
- "!include public_v_user_breakoutRoom.yaml"
- "!include public_v_user_camera.yaml"