start and stop recording
This commit is contained in:
parent
e388c18e03
commit
3129d90318
@ -31,6 +31,7 @@ class VideoCall {
|
||||
this.onCreateLocalVideoStream = options.onCreateLocalVideoStream;
|
||||
this.remoteParticipantStateChanged = options.remoteParticipantStateChanged;
|
||||
this.remoteVideoIsAvailableChanged = options.remoteVideoIsAvailableChanged;
|
||||
this.onGetServerCallID = options.onGetServerCallID;
|
||||
|
||||
const tokenCredential = new AzureCommunicationTokenCredential(userAccessToken);
|
||||
|
||||
@ -58,15 +59,10 @@ class VideoCall {
|
||||
const localVideoStream = await this.createLocalVideoStream();
|
||||
const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
|
||||
|
||||
const roomCallLocator = { roomId: roomId };
|
||||
let call = this.callAgent.join(roomCallLocator, { videoOptions });
|
||||
this.subscribeToCall(call);
|
||||
|
||||
this.callAgent.on('callsUpdated', e => {
|
||||
e.added.forEach((addedCall) => {
|
||||
addedCall.on('stateChanged', () => {
|
||||
if (addedCall.state === 'Connected') {
|
||||
debugger;
|
||||
addedCall.info.getServerCallId().then(result => {
|
||||
this.onGetServerCallID?.(result);
|
||||
}).catch(err => {
|
||||
@ -76,6 +72,10 @@ class VideoCall {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const roomCallLocator = { roomId: roomId };
|
||||
let call = this.callAgent.join(roomCallLocator, { videoOptions });
|
||||
this.subscribeToCall(call);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -14,13 +14,15 @@ namespace EnotaryoPH.Web.Common.Services
|
||||
private readonly CommunicationIdentityClient _communicationIdentityClient;
|
||||
private readonly RoomsClient _roomsClient;
|
||||
private readonly CallAutomationClient _callAutomationClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public ConferenceSheduleService(NotaryoDBContext dbContext, CommunicationIdentityClient communicationIdentityClient, RoomsClient roomsClient, CallAutomationClient callAutomationClient)
|
||||
public ConferenceSheduleService(NotaryoDBContext dbContext, CommunicationIdentityClient communicationIdentityClient, RoomsClient roomsClient, CallAutomationClient callAutomationClient, IConfiguration configuration)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_communicationIdentityClient = communicationIdentityClient;
|
||||
_roomsClient = roomsClient;
|
||||
_callAutomationClient = callAutomationClient;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public async Task<Guid> GetOrCreateScheduleIDAsync(Guid transaction_UID)
|
||||
@ -105,19 +107,20 @@ namespace EnotaryoPH.Web.Common.Services
|
||||
_dbContext.Update(schedule);
|
||||
}
|
||||
_dbContext.SaveChanges();
|
||||
schedule_UID = schedule.LawyerVideoConferenceSchedule_UID;
|
||||
}
|
||||
}
|
||||
|
||||
return schedule_UID;
|
||||
}
|
||||
|
||||
public async Task StartRecordingAsync(Guid transaction_UID)
|
||||
public async Task StartRecordingAsync(Guid transaction_UID, string serverCallID)
|
||||
{
|
||||
var transactionEntity = _dbContext.Transactions
|
||||
.FirstOrDefault(t => t.Transaction_UID == transaction_UID) ?? throw new ArgumentException("Transaction not found.");
|
||||
|
||||
var existingSchedule = _dbContext.LawyerVideoConferenceSchedules.FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID) ?? throw new ArgumentException("Schedule not found.");
|
||||
if (string.IsNullOrEmpty(existingSchedule.ServerCallID))
|
||||
if (string.IsNullOrEmpty(existingSchedule.ServerCallID) && string.IsNullOrEmpty(serverCallID))
|
||||
{
|
||||
throw new ArgumentException("ServerCallID is not set for this transaction.");
|
||||
}
|
||||
@ -127,8 +130,18 @@ namespace EnotaryoPH.Web.Common.Services
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(serverCallID))
|
||||
{
|
||||
existingSchedule.ServerCallID = serverCallID;
|
||||
}
|
||||
|
||||
CallLocator callLocator = new ServerCallLocator(existingSchedule.ServerCallID);
|
||||
var recordingResult = await _callAutomationClient.GetCallRecording().StartAsync(new StartRecordingOptions(callLocator) { RecordingStorage = RecordingStorage.CreateAzureBlobContainerRecordingStorage(new Uri("")) });
|
||||
var uri = _configuration.GetValue<string>("UriRecordingBloblContainer") ?? string.Empty;
|
||||
var recordingResult = await _callAutomationClient
|
||||
.GetCallRecording().StartAsync(new StartRecordingOptions(callLocator)
|
||||
{
|
||||
RecordingStorage = RecordingStorage.CreateAzureBlobContainerRecordingStorage(new Uri(uri))
|
||||
});
|
||||
existingSchedule.RecordingID = recordingResult.Value.RecordingId;
|
||||
_dbContext.Update(existingSchedule);
|
||||
_dbContext.SaveChanges();
|
||||
@ -150,7 +163,6 @@ namespace EnotaryoPH.Web.Common.Services
|
||||
throw new ArgumentException("Recording ID is not set for this transaction.");
|
||||
}
|
||||
|
||||
CallLocator callLocator = new ServerCallLocator(existingSchedule.ServerCallID);
|
||||
await _callAutomationClient.GetCallRecording().StopAsync(existingSchedule.RecordingID);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
{
|
||||
Task<Guid> GetOrCreateScheduleIDAsync(Guid transaction_UID);
|
||||
|
||||
Task StartRecordingAsync(Guid transaction_UID);
|
||||
Task StartRecordingAsync(Guid transaction_UID, string serverCallID);
|
||||
|
||||
Task StopRecordingAsync(Guid transaction_UID);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
@using System.Text.Json
|
||||
@model EnotaryoPH.Web.Pages.Participant.VideoCall.RoomModel
|
||||
@{
|
||||
Layout = "_Blank";
|
||||
}
|
||||
|
||||
@section Head {
|
||||
@ -64,7 +65,19 @@
|
||||
}
|
||||
|
||||
<div class="container-fluid py-3" id="videoGrid-container">
|
||||
<div class="row g-2" id="videoGrid">
|
||||
<div class="d-flex mb-1">
|
||||
<div>
|
||||
<span>32:04</span>
|
||||
</div>
|
||||
<div class="flex-fill"></div>
|
||||
<div>
|
||||
<a href="#" id="ViewDocument" class="btn btn-sm btn-secondary">View Document</a>
|
||||
<a href="#" id="Approve" class="btn btn-sm btn-success">Approve</a>
|
||||
<a href="#" id="Reject" class="btn btn-sm btn-danger">Reject</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row g-2" id="VideoGrid">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -74,100 +87,17 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<input type="hidden" value="@JsonSerializer.Serialize(Model.Participants)" />
|
||||
<input type="hidden" id="Participants" value="@(System.Web.HttpUtility.JavaScriptStringEncode(JsonSerializer.Serialize(Model.Participants)).Replace("\\", ""))" />
|
||||
<input type="hidden" id="CommunicationUserToken" value="@Model.CommunicationUserToken" />
|
||||
<input type="hidden" id="CommunicationUserId" value="@Model.CommunicationUserId" />
|
||||
<input type="hidden" id="CommunicationRoomId" value="@Model.CommunicationRoomId" />
|
||||
|
||||
<form method="post" asp-page-handler="StartRecording">
|
||||
<input type="hidden" asp-for="ServerCallID" />
|
||||
<input type="hidden" asp-for="Transaction_UID" />
|
||||
</form>
|
||||
|
||||
@section Scripts {
|
||||
<script type="text/javascript" src="/dist/_jfa.js"></script>
|
||||
<script>
|
||||
|
||||
let participants = JSON.parse('@Html.Raw(JsonSerializer.Serialize(Model.Participants))');
|
||||
function updateGrid() {
|
||||
const videoGrid = document.getElementById('videoGrid');
|
||||
videoGrid.innerHTML = '';
|
||||
|
||||
participants.forEach((participant, index) => {
|
||||
const col = document.createElement('div');
|
||||
col.className = 'participant-col';
|
||||
var tmpl = document.getElementById("templateVideo").cloneNode(true).content;
|
||||
let vidcontainer = tmpl.querySelector(".video-container")
|
||||
if (vidcontainer) {
|
||||
vidcontainer.id = participant.Id;
|
||||
vidcontainer.classList.add(participant.Id);
|
||||
vidcontainer.classList.add(participant.Type == 'Notary' ? 'local-video-container' : 'remote-video-container');
|
||||
}
|
||||
let participantName = tmpl.querySelector(".participant-name")
|
||||
if (participantName) {
|
||||
participantName.textContent = participant.DisplayName;
|
||||
}
|
||||
col.appendChild(tmpl);
|
||||
videoGrid.appendChild(col);
|
||||
});
|
||||
|
||||
// Dynamically adjust grid columns based on participant count
|
||||
const count = participants.length;
|
||||
const cols = count <= 2 ? 'col-12 col-sm-12 offset-md-1 col-md-10 offset-lg-0 col-lg-6' :
|
||||
count <= 4 ? 'col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6' :
|
||||
count <= 8 ? 'col-6 col-md-6 col-lg-4 col-xl-4' :
|
||||
count <= 9 ? 'col-4' :
|
||||
'col-6 col-sm-4 col-lg-3';
|
||||
|
||||
document.querySelectorAll('.participant-col').forEach(el => {
|
||||
el.className = `participant-col ${cols}`;
|
||||
});
|
||||
|
||||
const fluid = count <= 2 ? 'container-fluid' :
|
||||
count <= 8 ? 'container-xxl' :
|
||||
'container-xl'
|
||||
document.getElementById('videoGrid-container').className = `${fluid} py-3`;
|
||||
}
|
||||
|
||||
async function initVideoCall() {
|
||||
let userAccessToken = '@Model.CommunicationUserToken';
|
||||
const videoCall = jfa.communication.videocall;
|
||||
let options = {
|
||||
onCreateLocalVideoStream: function(el) {
|
||||
if (el) {
|
||||
el.style['transform'] = '';
|
||||
let notary = participants.find(p => p.RoomUserID == '@Model.CommunicationUserId');
|
||||
document.getElementById(notary.Id).appendChild(el);
|
||||
}
|
||||
},
|
||||
remoteVideoIsAvailableChanged: function(e) {
|
||||
let participant = participants.find(p => p.RoomUserID == e.participantId);
|
||||
if (participant) {
|
||||
e.el.querySelector('video').style['object-fit'] = 'cover';
|
||||
document.getElementById(participant.Id).appendChild(e.el);
|
||||
}
|
||||
},
|
||||
onGetServerCallID: function(e) {
|
||||
debugger;
|
||||
let url = jfa.utilities.routing.getCurrentURLWithHandler("StartRecording");
|
||||
debugger;
|
||||
url.searchParams.append("ServerCallID", e.serverCallId || "");
|
||||
jfa.utilities.request.post(url, {})
|
||||
.then(resp => {
|
||||
if (resp.ok === true) {
|
||||
jfa.page.reload();
|
||||
}
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
}
|
||||
};
|
||||
await videoCall.init(userAccessToken, options);
|
||||
videoCall.joinRoom('@Model.CommunicationRoomId');
|
||||
}
|
||||
|
||||
if (document.readyState !== 'loading') {
|
||||
init();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
}
|
||||
|
||||
async function init() {
|
||||
updateGrid();
|
||||
window.addEventListener('resize', updateGrid);
|
||||
|
||||
await initVideoCall();
|
||||
}
|
||||
</script>
|
||||
<script src="~/Pages/Participant/VideoCall/Room.cshtml.js" asp-append-version="true"></script>
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
||||
{
|
||||
public class RoomModel : PageModel
|
||||
{
|
||||
private const int VideoConferenceExpirationInHours = 2;
|
||||
private readonly IConferenceSheduleService _conferenceSheduleService;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly NotaryoDBContext _dbContext;
|
||||
@ -22,10 +23,7 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
||||
|
||||
public async Task<IActionResult> OnGetAsync()
|
||||
{
|
||||
_Transaction = _dbContext.Transactions
|
||||
.Include(t => t.TransactionSignatories)
|
||||
.Include(t => t.Principal)
|
||||
.FirstOrDefault(t => t.Transaction_UID == Transaction_UID);
|
||||
LoadTransaction();
|
||||
if (_Transaction == null)
|
||||
{
|
||||
return NotFound();
|
||||
@ -48,12 +46,20 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
_LawyerVideoConferenceSchedule = _dbContext.LawyerVideoConferenceSchedules
|
||||
.Include(sched => sched.LawyerVideoConferenceParticipants)
|
||||
.ThenInclude(p => p.Participant)
|
||||
.Include(sched => sched.Lawyer)
|
||||
.ThenInclude(l => l.User)
|
||||
.FirstOrDefault(sched => sched.LawyerVideoConferenceSchedule_UID == schedule_UID);
|
||||
LoadVideoConferenceSchedule(schedule_UID);
|
||||
|
||||
if ((DateTime.UtcNow - _LawyerVideoConferenceSchedule.MeetingDate).TotalHours > VideoConferenceExpirationInHours)
|
||||
{
|
||||
if (!_LawyerVideoConferenceSchedule.Status.IsInList(VideoConferenceStatus.Abandoned, VideoConferenceStatus.Completed))
|
||||
{
|
||||
_LawyerVideoConferenceSchedule.Status = nameof(VideoConferenceStatus.Expired);
|
||||
_dbContext.Update(_LawyerVideoConferenceSchedule);
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
TempData["Warning"] = "The video conference has expired.";
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
CommunicationUserToken = currentUser.Role == nameof(UserType.Notary)
|
||||
? _LawyerVideoConferenceSchedule.MeetingRoomTokenID
|
||||
@ -66,7 +72,33 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
||||
return Page();
|
||||
}
|
||||
|
||||
public async Task OnPostStartRecordingAsync() => await _conferenceSheduleService.StartRecordingAsync(Transaction_UID);
|
||||
public async Task OnPostStartRecordingAsync()
|
||||
{
|
||||
LoadTransaction();
|
||||
var schedule_UID = await _conferenceSheduleService.GetOrCreateScheduleIDAsync(Transaction_UID);
|
||||
LoadVideoConferenceSchedule(schedule_UID);
|
||||
await _conferenceSheduleService.StartRecordingAsync(Transaction_UID, ServerCallID);
|
||||
}
|
||||
|
||||
public async Task OnPostStopRecordingAsync()
|
||||
{
|
||||
LoadTransaction();
|
||||
var schedule_UID = await _conferenceSheduleService.GetOrCreateScheduleIDAsync(Transaction_UID);
|
||||
LoadVideoConferenceSchedule(schedule_UID);
|
||||
await _conferenceSheduleService.StopRecordingAsync(Transaction_UID);
|
||||
}
|
||||
|
||||
private void LoadTransaction() => _Transaction = _dbContext.Transactions
|
||||
.Include(t => t.TransactionSignatories)
|
||||
.Include(t => t.Principal)
|
||||
.FirstOrDefault(t => t.Transaction_UID == Transaction_UID);
|
||||
|
||||
private void LoadVideoConferenceSchedule(Guid schedule_UID) => _LawyerVideoConferenceSchedule = _dbContext.LawyerVideoConferenceSchedules
|
||||
.Include(sched => sched.LawyerVideoConferenceParticipants)
|
||||
.ThenInclude(p => p.Participant)
|
||||
.Include(sched => sched.Lawyer)
|
||||
.ThenInclude(l => l.User)
|
||||
.FirstOrDefault(sched => sched.LawyerVideoConferenceSchedule_UID == schedule_UID);
|
||||
|
||||
public string CommunicationRoomId { get; private set; }
|
||||
|
||||
@ -101,7 +133,7 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
||||
}
|
||||
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public string ServerCallingID { get; set; }
|
||||
public string ServerCallID { get; set; }
|
||||
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public Guid Transaction_UID { get; set; }
|
||||
|
@ -0,0 +1,119 @@
|
||||
"use strict";
|
||||
(async function () {
|
||||
let
|
||||
control_approve = document.getElementById("Approve"),
|
||||
control_communicationRoomId = document.getElementById("CommunicationRoomId"),
|
||||
control_communicationUserId = document.getElementById("CommunicationUserId"),
|
||||
control_communicationUserToken = document.getElementById("CommunicationUserToken"),
|
||||
control_participants = document.getElementById("Participants"),
|
||||
control_reject = document.getElementById("Reject"),
|
||||
control_templateVideo = document.getElementById("templateVideo"),
|
||||
control_videoGrid = document.getElementById("VideoGrid"),
|
||||
control_videoGridContainer = document.getElementById("videoGrid-container"),
|
||||
control_viewDocument = document.getElementById("ViewDocument"),
|
||||
control_serverCallIID = document.getElementById("ServerCallID"),
|
||||
x = 1;
|
||||
|
||||
let participants = JSON.parse(control_participants.value);
|
||||
|
||||
async function _initVideoCall() {
|
||||
let userAccessToken = control_communicationUserToken.value;
|
||||
const videoCall = jfa.communication.videocall;
|
||||
let options = {
|
||||
onCreateLocalVideoStream: function (el) {
|
||||
if (el) {
|
||||
el.style['transform'] = '';
|
||||
let notary = participants.find(p => p.RoomUserID == control_communicationUserId.value);
|
||||
document.getElementById(notary.Id).appendChild(el);
|
||||
}
|
||||
},
|
||||
remoteVideoIsAvailableChanged: function (e) {
|
||||
let participant = participants.find(p => p.RoomUserID == e.participantId);
|
||||
if (participant) {
|
||||
e.el.querySelector('video').style['object-fit'] = 'cover';
|
||||
document.getElementById(participant.Id).appendChild(e.el);
|
||||
}
|
||||
},
|
||||
onGetServerCallID: function (serverCallId) {
|
||||
let url = jfa.utilities.routing.getCurrentURLWithHandler("StartRecording");
|
||||
url.searchParams.append("ServerCallID", serverCallId);
|
||||
jfa.utilities.request.post(url, {})
|
||||
.then(resp => {
|
||||
if (resp.ok === true) {
|
||||
console.log("started recording");
|
||||
}
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
}
|
||||
};
|
||||
await videoCall.init(userAccessToken, options);
|
||||
videoCall.joinRoom(control_communicationRoomId.value);
|
||||
}
|
||||
|
||||
function _updateGrid() {
|
||||
control_videoGrid.innerHTML = '';
|
||||
|
||||
participants.forEach((participant, index) => {
|
||||
const col = document.createElement('div');
|
||||
col.className = 'participant-col';
|
||||
const tmpl = control_templateVideo.cloneNode(true).content;
|
||||
const vidcontainer = tmpl.querySelector(".video-container")
|
||||
if (vidcontainer) {
|
||||
vidcontainer.id = participant.Id;
|
||||
vidcontainer.classList.add(participant.Id);
|
||||
vidcontainer.classList.add(participant.Type == 'Notary' ? 'local-video-container' : 'remote-video-container');
|
||||
}
|
||||
let participantName = tmpl.querySelector(".participant-name")
|
||||
if (participantName) {
|
||||
participantName.textContent = participant.DisplayName;
|
||||
}
|
||||
col.appendChild(tmpl);
|
||||
control_videoGrid.appendChild(col);
|
||||
});
|
||||
|
||||
// Dynamically adjust grid columns based on participant count
|
||||
const count = participants.length;
|
||||
const cols = count <= 2 ? 'col-12 col-sm-12 offset-md-1 col-md-10 offset-lg-0 col-lg-6' :
|
||||
count <= 4 ? 'col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6' :
|
||||
count <= 8 ? 'col-6 col-md-6 col-lg-4 col-xl-4' :
|
||||
count <= 9 ? 'col-4' :
|
||||
'col-6 col-sm-4 col-lg-3';
|
||||
|
||||
document.querySelectorAll('.participant-col').forEach(el => {
|
||||
el.className = `participant-col ${cols}`;
|
||||
});
|
||||
|
||||
const fluid = count <= 2 ? 'container-fluid' :
|
||||
count <= 8 ? 'container-xxl' :
|
||||
'container-xl'
|
||||
control_videoGridContainer.className = `${fluid} py-3`;
|
||||
}
|
||||
|
||||
function _bindEvents() {
|
||||
window.addEventListener('resize', _updateGrid);
|
||||
control_viewDocument.addEventListener("click", function () {
|
||||
alert('not yet implemented');
|
||||
});
|
||||
control_approve.addEventListener("click", function () {
|
||||
let url = jfa.utilities.routing.getCurrentURLWithHandler("StopRecording");
|
||||
jfa.utilities.request.post(url, {})
|
||||
.then(resp => {
|
||||
if (resp.ok === true) {
|
||||
console.log("stopped recording");
|
||||
}
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
});
|
||||
control_reject.addEventListener("click", function () {
|
||||
alert('not yet implemented');
|
||||
});
|
||||
}
|
||||
|
||||
async function _init() {
|
||||
_bindEvents();
|
||||
_updateGrid();
|
||||
await _initVideoCall();
|
||||
}
|
||||
|
||||
await _init();
|
||||
})();
|
Loading…
x
Reference in New Issue
Block a user