Merge pull request #3591 from oswaldoacauan/refactor-api-captions
[HTML5] Refactor API Closed Captions
This commit is contained in:
commit
1577fbd35a
1
bigbluebutton-html5/.gitignore
vendored
1
bigbluebutton-html5/.gitignore
vendored
@ -5,6 +5,7 @@ node_modules/
|
||||
.meteor/dev_bundle
|
||||
|
||||
private/config/server/*
|
||||
!private/config/server/app.yaml
|
||||
!private/config/server/redis.yaml
|
||||
!private/config/server/shell.yaml
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import handleCaptionHistory from './handlers/captionHistory';
|
||||
import handleCaptionUpdate from './handlers/captionUpdate';
|
||||
import handleCaptionOwnerUpdate from './handlers/captionOwnerUpdate';
|
||||
|
||||
RedisPubSub.on('send_caption_history_reply_message', handleCaptionHistory);
|
||||
RedisPubSub.on('edit_caption_history_message', handleCaptionUpdate);
|
||||
RedisPubSub.on('update_caption_owner_message', handleCaptionOwnerUpdate);
|
@ -0,0 +1,64 @@
|
||||
import _ from 'underscore';
|
||||
import Captions from '/imports/api/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionHistory({ payload }) {
|
||||
if (!inReplyToHTML5Client({ payload })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SERVER_CONFIG = Meteor.settings.app;
|
||||
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
|
||||
|
||||
const meetingId = payload.meeting_id;
|
||||
const locale = payload.locale;
|
||||
const captionHistory = payload.caption_history;
|
||||
|
||||
check(meetingId, String);
|
||||
check(captionHistory, Object);
|
||||
|
||||
let captionsAdded = [];
|
||||
_.each(captionHistory, (caption, locale) => {
|
||||
let ownerId = caption[0];
|
||||
let captions = caption[1].slice(0);
|
||||
let chunks = [];
|
||||
|
||||
if (captions.length === 0) {
|
||||
chunks.push('');
|
||||
} else while (captions.length > 0) {
|
||||
if (captions.length > CAPTION_CHUNK_LENGTH) {
|
||||
chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
|
||||
captions = captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
} else {
|
||||
chunks.push(captions);
|
||||
captions = captions.slice(captions.length);
|
||||
}
|
||||
}
|
||||
|
||||
const selectorToRemove = {
|
||||
meetingId,
|
||||
locale,
|
||||
'captionHistory.index': { $gt: (chunks.length - 1) },
|
||||
};
|
||||
|
||||
Captions.remove(selectorToRemove);
|
||||
|
||||
chunks.forEach((captions, index) => {
|
||||
let captionHistoryObject = {
|
||||
locale,
|
||||
ownerId,
|
||||
captions,
|
||||
index,
|
||||
next: (index < chunks.length - 1) ? index + 1 : undefined,
|
||||
};
|
||||
|
||||
captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return captionsAdded;
|
||||
};
|
@ -0,0 +1,50 @@
|
||||
import Captions from '/imports/api/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionOwnerUpdate({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const locale = payload.locale;
|
||||
const ownerId = payload.owner_id;
|
||||
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
check(ownerId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
locale,
|
||||
};
|
||||
|
||||
let modifier = {
|
||||
$set: {
|
||||
'captionHistory.ownerId': ownerId,
|
||||
},
|
||||
};
|
||||
|
||||
const Caption = Captions.findOne(selector);
|
||||
|
||||
if (!Caption) {
|
||||
const captionHistory = {
|
||||
ownerId,
|
||||
captions: '',
|
||||
index: 0,
|
||||
next: null,
|
||||
};
|
||||
|
||||
return addCaption(meetingId, locale, captionHistory);
|
||||
}
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Updating captions owner: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.verbose(`Update caption owner locale=${locale} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Captions.update(selector, modifier, { multi: true }, cb);
|
||||
};
|
@ -1,11 +1,21 @@
|
||||
import Captions from '/imports/api/captions';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export function updateCaptionsCollection(meetingId, locale, payload) {
|
||||
export default function handleCaptionUpdate({ payload }) {
|
||||
const SERVER_CONFIG = Meteor.settings.app;
|
||||
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
|
||||
|
||||
const meetingId = payload.meeting_id;
|
||||
const locale = payload.locale;
|
||||
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
|
||||
let captionsObjects = Captions.find({
|
||||
meetingId: meetingId,
|
||||
locale: locale,
|
||||
meetingId,
|
||||
locale,
|
||||
}, {
|
||||
sort: {
|
||||
locale: 1,
|
||||
@ -93,18 +103,18 @@ export function updateCaptionsCollection(meetingId, locale, payload) {
|
||||
//looking for the strings which exceed the limit and split them into multiple objects
|
||||
let maxIndex = captionsObjects.length - 1;
|
||||
for (i = 0; i < objectsToUpdate.length; i++) {
|
||||
if (objectsToUpdate[i].captionHistory.captions.length > 1000) {
|
||||
if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
|
||||
//string is too large. Check if the next object exists and if it can
|
||||
//accomodate the part of the string that exceeds the limits
|
||||
let _nextIndex = objectsToUpdate[i].captionHistory.next;
|
||||
if (_nextIndex != null &&
|
||||
captionsObjects[_nextIndex].captionHistory.captions.length < 1000) {
|
||||
captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
|
||||
|
||||
let extraString = objectsToUpdate[i].captionHistory.captions.slice(1000);
|
||||
let extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
|
||||
//could assign it directly, but our linter complained
|
||||
let _captions = objectsToUpdate[i].captionHistory.captions;
|
||||
_captions = _captions.slice(0, 1000);
|
||||
_captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
|
||||
objectsToUpdate[i].captionHistory.captions = _captions;
|
||||
|
||||
//check to see if the next object was added to objectsToUpdate array
|
||||
@ -125,8 +135,8 @@ export function updateCaptionsCollection(meetingId, locale, payload) {
|
||||
//need to take a current object out of the objectsToUpdate and add it back after
|
||||
//every other object, so that Captions collection could be updated in a proper order
|
||||
let tempObj = objectsToUpdate.splice(i, 1);
|
||||
let extraString = tempObj[0].captionHistory.captions.slice(1000);
|
||||
tempObj[0].captionHistory.captions = tempObj[0].captionHistory.captions.slice(0, 1000);
|
||||
let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
tempObj[0].captionHistory.captions = tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
|
||||
|
||||
maxIndex += 1;
|
||||
let tempIndex = tempObj[0].captionHistory.next;
|
||||
@ -139,13 +149,13 @@ export function updateCaptionsCollection(meetingId, locale, payload) {
|
||||
captionHistory: {
|
||||
locale: locale,
|
||||
ownerId: tempObj[0].captionHistory.ownerId,
|
||||
captions: extraString.slice(0, 1000),
|
||||
captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
|
||||
index: maxIndex,
|
||||
next: null,
|
||||
},
|
||||
};
|
||||
maxIndex += 1;
|
||||
extraString = extraString.slice(1000);
|
||||
extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
|
||||
if (extraString.length > 0) {
|
||||
entry.captionHistory.next = maxIndex;
|
||||
} else {
|
||||
@ -161,25 +171,11 @@ export function updateCaptionsCollection(meetingId, locale, payload) {
|
||||
}
|
||||
}
|
||||
|
||||
//updating the database
|
||||
for (i = 0; i < objectsToUpdate.length; i++) {
|
||||
Captions.upsert(
|
||||
{
|
||||
_id: objectsToUpdate[i]._id,
|
||||
meetingId: objectsToUpdate[i].meetingId,
|
||||
locale: objectsToUpdate[i].locale,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
meetingId: meetingId,
|
||||
locale: locale,
|
||||
'captionHistory.locale': locale,
|
||||
'captionHistory.ownerId': objectsToUpdate[i].captionHistory.ownerId,
|
||||
'captionHistory.captions': objectsToUpdate[i].captionHistory.captions,
|
||||
'captionHistory.next': objectsToUpdate[i].captionHistory.next,
|
||||
'captionHistory.index': objectsToUpdate[i].captionHistory.index,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
let captionsAdded = [];
|
||||
objectsToUpdate.forEach(entry => {
|
||||
const { _id, meetingId, locale, captionHistory } = entry;
|
||||
captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
|
||||
});
|
||||
|
||||
return captionsAdded;
|
||||
};
|
3
bigbluebutton-html5/imports/api/captions/server/index.js
Normal file
3
bigbluebutton-html5/imports/api/captions/server/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import './eventHandlers';
|
||||
import './methods';
|
||||
import './publishers';
|
@ -0,0 +1,4 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.methods({
|
||||
});
|
@ -0,0 +1,49 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Captions from '/imports/api/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function addCaption(meetingId, locale, captionHistory, id = false) {
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
check(captionHistory, Object);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
locale,
|
||||
};
|
||||
|
||||
if (id) {
|
||||
selector._id = id;
|
||||
} else {
|
||||
selector['captionHistory.index'] = captionHistory.index;
|
||||
}
|
||||
|
||||
let modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
locale,
|
||||
'captionHistory.locale': locale,
|
||||
'captionHistory.ownerId': captionHistory.ownerId,
|
||||
'captionHistory.captions':captionHistory.captions,
|
||||
'captionHistory.next': captionHistory.next,
|
||||
'captionHistory.index': captionHistory.index,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding caption to collection: ${err}`);
|
||||
}
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.verbose(`Added caption locale=${locale} meeting=${meetingId}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.verbose(`Upserted caption locale=${locale} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Captions.upsert(selector, modifier, cb);
|
||||
};
|
@ -1,46 +0,0 @@
|
||||
import Captions from '/imports/api/captions';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
export function addCaptionsToCollection(meetingId, locale, captionHistory) {
|
||||
let captionsString = captionHistory[1].slice(0);
|
||||
let captions = [];
|
||||
|
||||
if (captionsString.length > 0) {
|
||||
while (captionsString.length > 0) {
|
||||
if (captionsString.length > 1000) {
|
||||
captions.push(captionsString.slice(0, 1000));
|
||||
captionsString = captionsString.slice(1000);
|
||||
} else {
|
||||
captions.push(captionsString);
|
||||
captionsString = captionsString.slice(captionsString.length);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
captions.push('');
|
||||
}
|
||||
|
||||
let i;
|
||||
let next;
|
||||
for (i = 0; i < captions.length; i++) {
|
||||
if (i < captions.length - 1) {
|
||||
next = i + 1;
|
||||
} else {
|
||||
next = undefined;
|
||||
}
|
||||
|
||||
const entry = {
|
||||
meetingId: meetingId,
|
||||
locale: locale,
|
||||
captionHistory: {
|
||||
locale: locale,
|
||||
ownerId: captionHistory[0],
|
||||
captions: captions[i],
|
||||
index: i,
|
||||
next: next,
|
||||
},
|
||||
};
|
||||
Captions.insert(entry);
|
||||
}
|
||||
|
||||
logger.info(`added captions locale=[${locale}]:meetingId=[${meetingId}].`);
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
import Captions from '/imports/api/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearCaptions(meetingId) {
|
||||
if (meetingId) {
|
||||
return Captions.remove({ meetingId, }, Logger.info(`Cleared Captions (${meetingId})`));
|
||||
}
|
||||
|
||||
return Captions.remove({}, Logger.info('Cleared Captions (all)'));
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
import Captions from '/imports/api/captions';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
// called on server start and meeting end
|
||||
export function clearCaptionsCollection() {
|
||||
const meetingId = arguments[0];
|
||||
if (meetingId != null) {
|
||||
return Captions.remove({
|
||||
meetingId: meetingId,
|
||||
}, logger.info(`cleared Captions Collection (meetingId: ${meetingId}!`));
|
||||
} else {
|
||||
return Captions.remove({}, logger.info('cleared Captions Collection (all meetings)!'));
|
||||
}
|
||||
};
|
@ -1,79 +0,0 @@
|
||||
import { eventEmitter } from '/imports/startup/server';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
import { addCaptionsToCollection } from './addCaptionsToCollection';
|
||||
import { updateCaptionsCollection } from './updateCaptionsCollection';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Captions from '/imports/api/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
eventEmitter.on('send_caption_history_reply_message', function (arg) {
|
||||
Logger.debug('message', JSON.stringify(arg));
|
||||
if (inReplyToHTML5Client(arg)) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
if (Captions.findOne({
|
||||
meetingId: meetingId,
|
||||
}) == null) {
|
||||
const captionHistory = arg.payload.caption_history;
|
||||
for (let locale in captionHistory) {
|
||||
addCaptionsToCollection(meetingId, locale, captionHistory[locale]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
eventEmitter.on('update_caption_owner_message', function (arg) {
|
||||
Logger.debug(JSON.stringify(arg));
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
let payload = arg.payload;
|
||||
|
||||
if (Captions.findOne({
|
||||
meetingId: meetingId,
|
||||
locale: payload.locale,
|
||||
}) != null) {
|
||||
Captions.update(
|
||||
{
|
||||
meetingId: meetingId,
|
||||
locale: payload.locale,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
'captionHistory.ownerId': payload.owner_id,
|
||||
},
|
||||
},
|
||||
{
|
||||
multi: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const entry = {
|
||||
meetingId: meetingId,
|
||||
locale: payload.locale,
|
||||
captionHistory: {
|
||||
locale: payload.locale,
|
||||
ownerId: payload.owner_id,
|
||||
captions: '',
|
||||
index: 0,
|
||||
length: 0,
|
||||
next: null,
|
||||
},
|
||||
};
|
||||
Captions.insert(entry);
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
eventEmitter.on('edit_caption_history_message', function (arg) {
|
||||
Logger.debug('edit_caption_history_message ' + JSON.stringify(arg));
|
||||
let payload = arg.payload;
|
||||
let meetingId = payload.meeting_id;
|
||||
let locale = payload.locale;
|
||||
|
||||
if (meetingId != null) {
|
||||
updateCaptionsCollection(meetingId, locale, payload);
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
@ -1,16 +0,0 @@
|
||||
import Captions from '/imports/api/captions';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
Meteor.publish('captions', function (credentials) {
|
||||
if (isAllowedTo('subscribeCaptions', credentials)) {
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
logger.info(`publishing captions for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Captions.find({
|
||||
meetingId: meetingId,
|
||||
});
|
||||
} else {
|
||||
this.error(new Meteor.Error(402, "The user was not authorized to subscribe for 'captions'"));
|
||||
}
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import Captions from '/imports/api/captions';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
|
||||
Meteor.publish('captions', function (credentials) {
|
||||
// TODO: Some publishers have ACL and others dont
|
||||
// if (isAllowedTo('subscribeCaptions', credentials)) {
|
||||
// this.error(new Meteor.Error(402, "The user was not authorized to subscribe for 'captions'"));
|
||||
// }
|
||||
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
Logger.verbose(`Publishing Captions for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Captions.find({ meetingId });
|
||||
});
|
@ -9,13 +9,12 @@ import clearShapes from '/imports/api/shapes/server/modifiers/clearShapes';
|
||||
import clearSlides from '/imports/api/slides/server/modifiers/clearSlides';
|
||||
import clearPolls from '/imports/api/polls/server/modifiers/clearPolls';
|
||||
import clearCursor from '/imports/api/cursor/server/modifiers/clearCursor';
|
||||
import { clearCaptionsCollection }
|
||||
from '/imports/api/captions/server/modifiers/clearCaptionsCollection';
|
||||
import clearCaptions from '/imports/api/captions/server/modifiers/clearCaptions';
|
||||
import clearPresentations from '/imports/api/presentations/server/modifiers/clearPresentations';
|
||||
|
||||
export default function clearMeetings() {
|
||||
return Meetings.remove({}, (err) => {
|
||||
clearCaptionsCollection();
|
||||
clearCaptions();
|
||||
clearChats();
|
||||
clearCursor();
|
||||
clearPresentations();
|
||||
|
@ -8,8 +8,7 @@ import clearShapes from '/imports/api/shapes/server/modifiers/clearShapes';
|
||||
import clearSlides from '/imports/api/slides/server/modifiers/clearSlides';
|
||||
import clearPolls from '/imports/api/polls/server/modifiers/clearPolls';
|
||||
import clearCursor from '/imports/api/cursor/server/modifiers/clearCursor';
|
||||
import { clearCaptionsCollection }
|
||||
from '/imports/api/captions/server/modifiers/clearCaptionsCollection';
|
||||
import clearCaptions from '/imports/api/captions/server/modifiers/clearCaptions';
|
||||
import clearPresentations from '/imports/api/presentations/server/modifiers/clearPresentations';
|
||||
|
||||
export default function removeMeeting(meetingId) {
|
||||
@ -25,7 +24,7 @@ export default function removeMeeting(meetingId) {
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
clearCaptionsCollection(meetingId);
|
||||
clearCaptions(meetingId);
|
||||
clearChats(meetingId);
|
||||
clearCursor(meetingId);
|
||||
clearPresentations(meetingId);
|
||||
|
@ -209,10 +209,10 @@ export default class App extends Component {
|
||||
<div className={styles.content}>
|
||||
{this.renderNavBar()}
|
||||
{this.renderMedia()}
|
||||
{this.renderClosedCaptions()}
|
||||
{this.renderActionsBar()}
|
||||
</div>
|
||||
{this.renderSidebar()}
|
||||
{this.renderClosedCaptions()}
|
||||
</section>
|
||||
{this.renderAudioElement()}
|
||||
{this.renderModal()}
|
||||
|
2
bigbluebutton-html5/private/config/server/app.yaml
Normal file
2
bigbluebutton-html5/private/config/server/app.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
app:
|
||||
captionsChunkLength: 1000
|
@ -23,9 +23,7 @@ import '/imports/api/shapes/server';
|
||||
|
||||
import '/imports/api/slides/server';
|
||||
|
||||
import '/imports/api/captions/server/publications';
|
||||
import '/imports/api/captions/server/modifiers/clearCaptionsCollection';
|
||||
import '/imports/api/captions/server/modifiers/eventHandlers';
|
||||
import '/imports/api/captions/server';
|
||||
|
||||
import '/imports/api/users/server/publications';
|
||||
import '/imports/api/users/server/methods/kickUser';
|
||||
|
Loading…
Reference in New Issue
Block a user