Skip to content

Commit 87233cf

Browse files
Recording mixed file
1 parent 4a336c1 commit 87233cf

20 files changed

+1172
-57393
lines changed

meeting-recording.json

Lines changed: 895 additions & 57360 deletions
Large diffs are not rendered by default.

src/main/kotlin/io/openfuture/openmessenger/configuration/AwsConfig.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ data class AwsConfig(
1010
var secretKey: String? = null,
1111
var region: String? = null,
1212
var attachmentsBucket: String? = null,
13+
var recordingsBucket: String? = null,
14+
var transcriptsBucket: String? = null,
1315
var cognito: Cognito = Cognito()
1416
)
1517

src/main/kotlin/io/openfuture/openmessenger/kurento/KurentoWebsocketConfigurer.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package io.openfuture.openmessenger.kurento
22

3+
import io.openfuture.openmessenger.assistant.gemini.GeminiService
34
import io.openfuture.openmessenger.kurento.groupcall.CallHandler
45
import io.openfuture.openmessenger.kurento.groupcall.RoomManager
6+
import io.openfuture.openmessenger.kurento.recording.RecordingCallHandler
7+
import io.openfuture.openmessenger.repository.MeetingNoteRepository
8+
import io.openfuture.openmessenger.service.RecordingManagementService
9+
import io.openfuture.openmessenger.service.SpeechToTextService
510
import org.kurento.client.KurentoClient
611
import org.springframework.beans.factory.annotation.Value
712
import org.springframework.context.annotation.Bean
@@ -10,13 +15,16 @@ import org.springframework.web.socket.config.annotation.EnableWebSocket
1015
import org.springframework.web.socket.config.annotation.WebSocketConfigurer
1116
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
1217
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean
13-
import io.openfuture.openmessenger.kurento.recording.RecordingCallHandler as RecordingCallHandler
1418

1519
@Configuration
1620
@EnableWebSocket
1721
class KurentoWebsocketConfigurer(
1822
@Value("\${kms.url}")
19-
val kurentoUrl: String
23+
val kurentoUrl: String,
24+
val recordingManagementService: RecordingManagementService,
25+
private val speechToTextService: SpeechToTextService,
26+
private val geminiService: GeminiService,
27+
private val meetingNoteRepository: MeetingNoteRepository
2028
) : WebSocketConfigurer {
2129
@Bean
2230
fun handler(): HelloWorldHandler {
@@ -63,7 +71,9 @@ class KurentoWebsocketConfigurer(
6371

6472
@Bean
6573
fun callHandler(): RecordingCallHandler {
66-
return RecordingCallHandler(kurentoClient(), recordingUserRegistry())
74+
return RecordingCallHandler(
75+
kurentoClient(), recordingUserRegistry(), recordingManagementService, speechToTextService, geminiService, meetingNoteRepository
76+
)
6777
}
6878

6979
}

src/main/kotlin/io/openfuture/openmessenger/kurento/recording/CallMediaPipeline.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class CallMediaPipeline(kurento: KurentoClient, from: String, to: String?) {
1515

1616
val composite: Composite = Composite.Builder(pipeline).build()
1717
val out = HubPort.Builder(composite).build()
18-
val recorder: RecorderEndpoint = RecorderEndpoint.Builder(pipeline, RECORDING_PATH + "combined" + RECORDING_EXT)
18+
val recordedFileUri = RECORDING_PATH + "combined" + RECORDING_EXT
19+
val recorder: RecorderEndpoint = RecorderEndpoint.Builder(pipeline, recordedFileUri)
1920
.build()
2021

2122
init {
@@ -57,7 +58,7 @@ class CallMediaPipeline(kurento: KurentoClient, from: String, to: String?) {
5758

5859
companion object {
5960
private val df = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss-S")
60-
val RECORDING_PATH: String = "file:///tmp/" + df.format(Date()) + "-"
61+
val RECORDING_PATH: String = "file:///tmp/recordings/" + df.format(Date()) + "-"
6162
const val RECORDING_EXT: String = ".webm"
6263
}
6364
}

src/main/kotlin/io/openfuture/openmessenger/kurento/recording/RecordingCallHandler.kt

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package io.openfuture.openmessenger.kurento.recording
33
import com.google.gson.Gson
44
import com.google.gson.GsonBuilder
55
import com.google.gson.JsonObject
6+
import io.openfuture.openmessenger.assistant.gemini.GeminiService
7+
import io.openfuture.openmessenger.repository.MeetingNoteRepository
8+
import io.openfuture.openmessenger.repository.entity.MeetingNoteEntity
9+
import io.openfuture.openmessenger.service.RecordingManagementService
10+
import io.openfuture.openmessenger.service.SpeechToTextService
611
import org.kurento.client.IceCandidate
712
import org.kurento.client.KurentoClient
813
import org.kurento.client.MediaPipeline
@@ -14,21 +19,27 @@ import org.springframework.web.socket.TextMessage
1419
import org.springframework.web.socket.WebSocketSession
1520
import org.springframework.web.socket.handler.TextWebSocketHandler
1621
import java.io.IOException
22+
import java.time.LocalDateTime
1723
import java.util.concurrent.ConcurrentHashMap
1824

1925
class RecordingCallHandler(
2026
private val kurento: KurentoClient,
2127
private val registry: UserRegistry,
28+
private val recordingManagementService: RecordingManagementService,
29+
private val speechToTextService: SpeechToTextService,
30+
private val geminiService: GeminiService,
31+
private val meetingNoteRepository: MeetingNoteRepository
2232
): TextWebSocketHandler() {
2333
val pipelines = ConcurrentHashMap<String, MediaPipeline?>()
34+
val recordings = ConcurrentHashMap<String, String>()
2435

2536
@Throws(Exception::class)
2637
public override fun handleTextMessage(session: WebSocketSession, message: TextMessage) {
2738
val jsonMessage = gson.fromJson(
2839
message.payload,
2940
JsonObject::class.java
3041
)
31-
val user = registry!!.getBySession(session)
42+
val user = registry.getBySession(session)
3243

3344
if (user != null) {
3445
log.debug("Incoming message from user '{}': {}", user.name, jsonMessage)
@@ -113,16 +124,16 @@ class RecordingCallHandler(
113124
private fun incomingCallResponse(callee: UserSession, jsonMessage: JsonObject) {
114125
val callResponse = jsonMessage["callResponse"].asString
115126
val from = jsonMessage["from"].asString
116-
val calleer = registry!!.getByName(from)
117-
val to = calleer?.callingTo
127+
val caller = registry.getByName(from)
128+
val to = caller?.callingTo
118129

119130
if ("accept" == callResponse) {
120131
log.debug("Accepted call from '{}' to '{}'", from, to)
121132

122-
val callMediaPipeline = CallMediaPipeline(
123-
kurento!!, from, to
124-
)
125-
pipelines[calleer!!.sessionId] = callMediaPipeline.pipeline
133+
val callMediaPipeline = CallMediaPipeline(kurento, from, to)
134+
recordings[caller!!.sessionId] = callMediaPipeline.recordedFileUri
135+
recordings[callee.sessionId] = callMediaPipeline.recordedFileUri
136+
pipelines[caller.sessionId] = callMediaPipeline.pipeline
126137
pipelines[callee.sessionId] = callMediaPipeline.pipeline
127138

128139
callee.setWebRtcEndpoint(callMediaPipeline.calleeWebRtcEp)
@@ -153,14 +164,14 @@ class RecordingCallHandler(
153164

154165
val callerSdpOffer = registry.getByName(from)?.sdpOffer
155166

156-
calleer!!.setWebRtcEndpoint(callMediaPipeline.callerWebRtcEp)
167+
caller.setWebRtcEndpoint(callMediaPipeline.callerWebRtcEp)
157168
callMediaPipeline.callerWebRtcEp.addIceCandidateFoundListener { event ->
158169
val response = JsonObject()
159170
response.addProperty("id", "iceCandidate")
160171
response.add("candidate", JsonUtils.toJsonObject(event.candidate))
161172
try {
162-
synchronized(calleer.session) {
163-
calleer.session.sendMessage(TextMessage(response.toString()))
173+
synchronized(caller.session) {
174+
caller.session.sendMessage(TextMessage(response.toString()))
164175
}
165176
} catch (e: IOException) {
166177
log.debug(e.message)
@@ -174,8 +185,8 @@ class RecordingCallHandler(
174185
response.addProperty("response", "accepted")
175186
response.addProperty("sdpAnswer", callerSdpAnswer)
176187

177-
synchronized(calleer) {
178-
calleer.sendMessage(response)
188+
synchronized(caller) {
189+
caller.sendMessage(response)
179190
}
180191

181192
callMediaPipeline.callerWebRtcEp.gatherCandidates()
@@ -185,15 +196,14 @@ class RecordingCallHandler(
185196
val response = JsonObject()
186197
response.addProperty("id", "callResponse")
187198
response.addProperty("response", "rejected")
188-
calleer!!.sendMessage(response)
199+
caller!!.sendMessage(response)
189200
}
190201
}
191202

192203
@Throws(IOException::class)
193204
fun stop(session: WebSocketSession) {
194-
// Both users can stop the communication. A 'stopCommunication'
195-
// message will be sent to the other peer.
196-
val stopperUser = registry!!.getBySession(session)
205+
val stopperUser = registry.getBySession(session)
206+
197207
if (stopperUser != null) {
198208
val stoppedUser =
199209
if ((stopperUser.callingFrom != null))
@@ -211,6 +221,36 @@ class RecordingCallHandler(
211221
stoppedUser.clear()
212222
}
213223
stopperUser.clear()
224+
225+
val caller = stopperUser.callingFrom ?: stoppedUser?.callingFrom
226+
val callee = stopperUser.callingTo ?: stoppedUser?.callingTo
227+
log.info("Caller = $caller, callee = $callee")
228+
229+
pipelines[stoppedUser?.sessionId]
230+
231+
val fileUriString = recordings[stoppedUser?.sessionId]
232+
fileUriString?.let { val uploadToS3 = recordingManagementService.uploadToS3(it)
233+
if (uploadToS3 == -1) {
234+
log.warn("No file was uploaded for call session ${stoppedUser?.sessionId}")
235+
return
236+
}
237+
val transcript = speechToTextService.extractTranscript(uploadToS3)
238+
val chat = geminiService.chat("Generate a summary from the following meeting record: {$transcript}")
239+
240+
val meetingNoteEntity = MeetingNoteEntity(
241+
caller,
242+
777,
243+
999,
244+
"[$caller, $callee]",
245+
callee,
246+
LocalDateTime.now(),
247+
1,
248+
LocalDateTime.now(),
249+
LocalDateTime.now(),
250+
chat
251+
)
252+
meetingNoteRepository.save(meetingNoteEntity)
253+
}
214254
}
215255
}
216256

@@ -294,11 +334,12 @@ class RecordingCallHandler(
294334
@Throws(Exception::class)
295335
override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) {
296336
stop(session)
297-
registry!!.removeBySession(session)
337+
registry.removeBySession(session)
298338
}
299339

300340
companion object {
301341
private val log: Logger = LoggerFactory.getLogger(RecordingCallHandler::class.java)
302342
private val gson: Gson = GsonBuilder().create()
303343
}
344+
304345
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.openfuture.openmessenger.repository
2+
3+
import io.openfuture.openmessenger.repository.entity.MeetingNoteEntity
4+
import org.springframework.data.jpa.repository.JpaRepository
5+
6+
interface MeetingNoteRepository : JpaRepository<MeetingNoteEntity, Long> {
7+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.openfuture.openmessenger.repository.entity
2+
3+
import jakarta.persistence.*
4+
import java.time.LocalDateTime
5+
import java.time.LocalDateTime.now
6+
7+
8+
@Entity
9+
@Table(name = "meeting_notes")
10+
class MeetingNoteEntity() {
11+
constructor(
12+
author: String?,
13+
chatId: Int?,
14+
groupChatId: Int?,
15+
members: String?,
16+
recipient: String?,
17+
generatedAt: LocalDateTime = now(),
18+
version: Int = 1,
19+
startTime: LocalDateTime?,
20+
endTime: LocalDateTime?,
21+
notes: String?
22+
): this() {
23+
this.author = author
24+
this.chatId = chatId
25+
this.groupChatId = groupChatId
26+
this.members = members
27+
this.recipient = recipient
28+
this.generatedAt = generatedAt
29+
this.version = version
30+
this.startTime = startTime
31+
this.endTime = endTime
32+
this.notes = notes
33+
}
34+
35+
@Id
36+
@GeneratedValue(strategy = GenerationType.IDENTITY)
37+
var id: Long? = null
38+
var author: String? = null
39+
var chatId: Int? = null
40+
var groupChatId: Int? = null
41+
var members: String? = null
42+
var recipient: String? = null
43+
var generatedAt: LocalDateTime = now()
44+
var version: Int = 1
45+
var startTime: LocalDateTime? = null
46+
var endTime: LocalDateTime? = null
47+
var notes: String? = null
48+
}

src/main/kotlin/io/openfuture/openmessenger/service/AttachmentService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface AttachmentService {
1515
fun uploadAndReturnId(file: MultipartFile): Int
1616

1717
@Throws(IOException::class)
18-
fun download(fileName: String?): ByteArray?
18+
fun download(fileName: String?, bucket: String): ByteArray?
1919

2020
fun downloadById(id: Int): ByteArray?
2121
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.openfuture.openmessenger.service
2+
3+
interface RecordingManagementService {
4+
fun list()
5+
fun uploadToS3(fileUri: String): Int
6+
}

src/main/kotlin/io/openfuture/openmessenger/service/SpeechToTextService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ package io.openfuture.openmessenger.service
22

33
interface SpeechToTextService {
44
fun extractTranscript(attachmentId: Int): String
5+
fun getTranscript(attachmentId: String): String
56
}

0 commit comments

Comments
 (0)