Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
883f42e
moderator.speaking element can be an array, if so agenda array will b…
ddfridley Mar 21, 2023
5536fef
removed unused stuff from story
ddfridley Mar 21, 2023
7dbedb0
wixstatic mediaSrc
ddfridley Mar 21, 2023
aadb17a
viewer-recorder story, log message on stream upload fail
ddfridley Mar 23, 2023
78d94ed
info in stream-upload
ddfridley Mar 23, 2023
5147616
info in streamuploadvideo
ddfridley Mar 23, 2023
2abb390
more info
ddfridley Mar 23, 2023
0cef155
upload error msg and can upload again
ddfridley Mar 24, 2023
05df8c0
order of on, and error event args
ddfridley Mar 24, 2023
b6ded15
catch errors, more info
ddfridley Mar 24, 2023
c2489c4
do not throw
ddfridley Mar 24, 2023
4aefcde
do not diddle emit
ddfridley Mar 24, 2023
3e37499
socket info
ddfridley Mar 24, 2023
056881a
socket.connected
ddfridley Mar 24, 2023
72dadc4
100K high water mark
ddfridley Mar 24, 2023
016e281
chunk info and settimeout
ddfridley Mar 25, 2023
6a4198c
without through2 pipe
ddfridley Mar 25, 2023
f00c460
progress updated in setTimout within pipe
ddfridley Mar 27, 2023
5326302
updated civil-server w latest socket.io
ddfridley Mar 27, 2023
082fd97
100ms delay after pipe
ddfridley Mar 27, 2023
0a70ed9
don't print the streams
ddfridley Mar 27, 2023
9be2763
no progress update in pipe
ddfridley Mar 27, 2023
853ff80
delay newPipe
ddfridley Mar 27, 2023
bc30632
long delay before newPipe
ddfridley Mar 27, 2023
b01215f
very long delay before newPipe
ddfridley Mar 27, 2023
cfa61d9
allow api to open stream
ddfridley Mar 28, 2023
7da50e3
allow api to open stream long
ddfridley Mar 28, 2023
52ee827
load the stream before api call to server
ddfridley Mar 28, 2023
4a49663
ss.error
ddfridley Mar 28, 2023
d477390
on data progress
ddfridley Mar 28, 2023
852303a
onAny listener
ddfridley Mar 28, 2023
51cab01
start pipe after 5 seconds
ddfridley Mar 28, 2023
2f30b60
send stream upload after 5 after pipe
ddfridley Mar 28, 2023
65a05dd
stream upload one tick after pipe
ddfridley Mar 28, 2023
6b16369
stream upload 100 tick after pipe
ddfridley Mar 28, 2023
b56582b
stream delay
ddfridley Mar 28, 2023
9568fe9
connected?
ddfridley Mar 28, 2023
210b56a
window.ssSocket
ddfridley Mar 28, 2023
95787c9
onAny in vr
ddfridley Mar 28, 2023
cff885c
stream after chunk
ddfridley Mar 28, 2023
fec9a8d
stream after delay after chunk
ddfridley Mar 28, 2023
25b3126
do not do stream from chunk
ddfridley Mar 28, 2023
2511423
check for connected more
ddfridley Mar 28, 2023
e7d8e58
track connected
ddfridley Mar 28, 2023
44b7610
counter info
ddfridley Mar 29, 2023
e04b18e
counter info done
ddfridley Mar 29, 2023
97316a4
more count info
ddfridley Mar 29, 2023
af36a05
now()
ddfridley Mar 29, 2023
6e7b61d
socket error messages
ddfridley Mar 29, 2023
61ec877
delay before pipe 2 x for api
ddfridley Mar 29, 2023
e05ef53
new order, send stream to api first
ddfridley Mar 29, 2023
ef0bf39
send api after all
ddfridley Mar 29, 2023
257276c
dev mode web pack
ddfridley Mar 29, 2023
a97d621
create ssSocket after bstream
ddfridley Mar 29, 2023
0e7f143
delay bstream
ddfridley Mar 29, 2023
c37256e
delay first upload
ddfridley Mar 29, 2023
c667705
don't logger props
ddfridley Mar 29, 2023
ceffaae
on disconect at ccwrapper
ddfridley Mar 30, 2023
6783021
no start time in ccwrapper
ddfridley Mar 30, 2023
53a795a
timeout for progressfunc
ddfridley Mar 30, 2023
74425e1
less disco messages, no logger before createParticipant
ddfridley Mar 30, 2023
d6996f4
import useState
ddfridley Mar 30, 2023
3f11cbd
trace is back, and props length
ddfridley Mar 30, 2023
c79f9d4
removing setTimeouts of testing
ddfridley Mar 30, 2023
c0d0897
loging props to cause upload error
ddfridley Mar 30, 2023
ce4978e
eventError function
ddfridley Mar 30, 2023
8c8b7b5
don't overwrite error with progress
ddfridley Mar 30, 2023
38c368f
error if socket not connected to start, removing debug info
ddfridley Mar 30, 2023
87c3cbe
connectED
ddfridley Mar 30, 2023
6b24d63
eventError to top
ddfridley Mar 30, 2023
774f07e
fixing error handling
ddfridley Mar 30, 2023
22e88e1
debug info gone
ddfridley Mar 30, 2023
e0b8c4f
through2 removed, onAny removed, log of props commented out so it won…
ddfridley Mar 30, 2023
59d65d5
webpack.prod not dev mode
ddfridley Mar 31, 2023
985d5cf
convesation header logo can be none. no need to grab socket erros in …
ddfridley Apr 4, 2023
2293720
title in conversation header in square layout
ddfridley Apr 4, 2023
bf89181
firstPage in ViewerRecorder
ddfridley Apr 7, 2023
d41c2d2
camera starts, and there is a begin button
ddfridley Apr 7, 2023
96978d3
move hangup button to left
ddfridley Apr 10, 2023
c8c426b
Begin disabled before video allowed. When video allowed, mic and vide…
ddfridley Apr 10, 2023
d6fe3cb
hangupButton.disabled implemented
ddfridley Apr 10, 2023
49355f5
can iframe from deliberations.us, after post input name filed is removed
ddfridley Apr 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,7 @@ tmp
.ssh/

#this is used by Jest-mongodb for testing only -
globalConfig.json
globalConfig.json

#pulled from civil-server
assets/images
3 changes: 3 additions & 0 deletions app/components/agenda-nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,8 @@ const useStyles = createUseStyles({
'border-bottom': '1px solid lightGray',
'padding-top': '0.5rem',
'padding-bottom': '0.25rem',
'&:last-child': {
borderBottom: 'none'
}
},
})
13 changes: 9 additions & 4 deletions app/components/begin-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import IconPlay from '../svgr/icon-play'

const useStyles = createUseStyles({
outer: {
width: '100%',
height: '100%',
'&:disabled': {
backgroundColor: 'gray',
},
opacity: props => props.opacity,
'& :hover': {
opacity: props => props.opacity * 0.8,
Expand All @@ -31,12 +36,12 @@ const useStyles = createUseStyles({
},
})

const BeginButton = ({ children, width, height, onClick, ...props }) => {
const BeginButton = ({ children, width, height, onClick, disabled, ...props }) => {
const classes = useStyles(props)
return (
<div className={classes.outer} title="Begin">
<IconPlay width={width} height={height} className={classes.icon} onClick={onClick} data-testid="begin-button" />
</div>
<button className={classes.outer} onClick={onClick} disabled={disabled} title="Begin">
<IconPlay width={width} height={height} className={classes.icon} data-testid="begin-button" />
</button>
)
}

Expand Down
29 changes: 11 additions & 18 deletions app/components/conversation-header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ const styles = {
boxContainer: {
width: 'max-content',
margin: '1vh',
position: 'absolute',
top: 0,
},
portrait: {},
}
Expand All @@ -137,20 +139,11 @@ function xxxx_xx_xxTommmdd_yyyy(str) {
}

const LogoLinks = ({ classes, logo }) => {
// curried function to make link
const link_image = src => classname => href => (
<a target="#" href={href}>
<img className={classes[classname]} src={src} />
</a>
)
const makeLink = link => {
//builds the link one html attribute at a time using currying
let link_html = link_image
for (const attribute in link) {
link_html = link_html(link[attribute])
}
return link_html
}

const makeLink = link => (
<a target="#" href={link.href}>
<img className={classes[link.classname]} src={link.src} />
</a>)

// Defines the logo link attribute values for logo links in the header
const list_of_links = {
Expand All @@ -169,15 +162,15 @@ const LogoLinks = ({ classes, logo }) => {
'https://res.cloudinary.com/hf6mryjpf/image/upload/v1578591434/assets/Candidate_Conversations_logo-stacked_300_res.png',
classname: 'logo',
href: 'https://ballotpedia.org/Candidate_Conversations',
},
}
}
const { enciv, undebate, ballotpedia } = list_of_links
// actual layout of different links
return (
<>
{' '}
{makeLink(enciv)}
{logo && logo === 'undebate' ? makeLink(undebate) : makeLink(ballotpedia)}
{logo !== 'none' ? (list_of_links[logo] ? makeLink(list_of_links[logo]) : makeLink(ballotpedia)) : null}
</>
)
}
Expand Down Expand Up @@ -281,8 +274,8 @@ class ConversationHeader extends React.Component {
<div id="bcon" className={classes['boxContainer']}>
{' '}
{makeBox('leftBoxContainer')('leftBox')('conversationTopicContent')(subject)}
{makeBox('rightBoxContainer')('rightBox')('conversationElectionDate')(
xxxx_xx_xxTommmdd_yyyy(bp_info && bp_info.election_date)
{bp_info?.election_date && makeBox('rightBoxContainer')('rightBox')('conversationElectionDate')(
xxxx_xx_xxTommmdd_yyyy(bp_info.election_date)
)}
</div>
</div>
Expand Down
218 changes: 115 additions & 103 deletions app/components/lib/create-participant.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict;'
import through2 from 'through2'
import { auto_quality, placeholder_image } from './cloudinary-urls'
import ss from '@sap_oss/node-socketio-stream'

/**
*
* createParticipant uploads the video blobs that were collected, and then creates a new Iota for the participant
Expand Down Expand Up @@ -29,119 +29,131 @@ if (typeof window !== 'undefined') {
}

export default function createParticipant(props, human, userId, name, progressFunc, listeningRound, listeningSeat) {
var transferred = 0
var totalSize = 0
var participant = { speaking: [], name: name }
var uploadQueue = []
try {
var transferred = 0
var totalSize = 0
var participant = { speaking: [], name: name }
var uploadQueue = []

let adjustedSpeakingBlobs = human.speakingBlobs.slice() // make a copy so we don't mutate the original
if (listeningSeat === 'speaking') {
if (typeof adjustedSpeakingBlobs[listeningRound] !== 'undefined')
logger.error("createParticpant didn't expect blob for listening in speakingBlobs")
adjustedSpeakingBlobs.splice(listeningRound, 1) // if listening is in the speaking seat - skip that round
}
function updateProgress(chunk) {
transferred += chunk.length
var percentComplete = Math.round((transferred / totalSize) * 100) + '%'
progressFunc && progressFunc({ progress: percentComplete, uploadComplete: false })
}
const eventError = (message) => {
transferred = 'error'
if (window.socket.disconnected) window.socket.open() // some problems with the pipe would cause the stream to disconnect. It's fixed but lets leave this here.
logger.error("createParticipant caught error", message) // but it might not make it to the sever if the transport may be broke
uploadQueue = [] // stop other files from being uploaded
try {
progressFunc?.({ progress: message, uploadComplete: false, uploadStarted: false, uploadError: true })
} catch (err) { } // if that doesn't work just continue
// then carry on
}

function upload(blob, seat, round) {
var file_name = userId + '-' + round + '-' + seat + new Date().toISOString().replace(/[^A-Z0-9]/gi, '') + '.mp4' // mp4 was put here to get around something with Apple - check in future
if (!window.socket.connected) return eventError('The connection to the server is down.')

// socketIo-streams does not seem to be passing call backs (undefined is received)
// so we are using a socket io event to send the response back
const responseUrl = url => {
// responses don't necessarily come in order
if (url) {
logger.trace('url', url)
url = auto_quality(url)
if (seat === 'speaking') {
// what if the come out of order -- to be determined
participant.speaking[round] = url // specify the round because the order is not assures - don't use push
} else participant.listening = url
} else {
logger.error('createParticipant.stream-upload-video failed', file_name)
}
if (
allThere(participant.speaking, adjustedSpeakingBlobs.length) &&
!!human.listeningBlob === !!participant.listening
) {
// have all of the pieces been uploaded
logger.trace('creat participant', participant)
var pIota = {
//participant iota
parentId: props.parentId || (props._id && props._id.toString()), // a viewer with a human has no parentId, but a recorder has the viewer as it's parentId
subject: 'Participant:' + props.subject,
description: 'A participant in the following discussion:' + props.description,
component: {
component: 'MergeParticipants',
participant: participant,
},
const ssSocket = ss(window.socket)

let adjustedSpeakingBlobs = human.speakingBlobs.slice() // make a copy so we don't mutate the original
if (listeningSeat === 'speaking') {
if (typeof adjustedSpeakingBlobs[listeningRound] !== 'undefined')
logger.error("createParticpant didn't expect blob for listening in speakingBlobs")
adjustedSpeakingBlobs.splice(listeningRound, 1) // if listening is in the speaking seat - skip that round
}
function updateProgress(length) {
if (transferred === 'error') return // don't overwrite the error with progressFunc
transferred += length
var percentComplete = Math.round((transferred / totalSize) * 100) + '%'
progressFunc?.({ progress: percentComplete, uploadComplete: false, uploadStarted: true, uploadError: false })
}

function upload(blob, seat, round) {
var file_name = userId + '-' + round + '-' + seat + new Date().toISOString().replace(/[^A-Z0-9]/gi, '') + '.mp4' // mp4 was put here to get around something with Apple - check in future
// socketIo-streams does not seem to be passing call backs (undefined is received)
// so we are using a socket io event to send the response back
const responseUrl = url => {
if (url) {
logger.trace('createParticipant upload url', url)
url = auto_quality(url)
if (seat === 'speaking') {
// what if the come out of order -- to be determined
participant.speaking[round] = url // specify the round because the order is not assures - don't use push
} else participant.listening = url
} else {
logger.error('createParticipant.stream-upload-video failed', file_name)
return eventError(`There was an error uploading the video no url`)
}
if (props.bp_info) {
// don't cause the property to exist in the Iota if there is none.
pIota.component.participant.bp_info = Object.assign({}, props.bp_info) // make a copy cause we are going to delete stuff
delete pIota.component.participant.bp_info.campaign_email
delete pIota.component.participant.bp_info.personal_email
if (props.bp_info.candidate_name) pIota.component.participant.name = props.bp_info.candidate_name
if ((uploadArgs = uploadQueue.shift())) {
return upload(...uploadArgs)
} else if (
allThere(participant.speaking, adjustedSpeakingBlobs.length) &&
!!human.listeningBlob === !!participant.listening
) {
// have all of the pieces been uploaded
logger.info('createParticipant upload complete')
progressFunc?.({ progress: 'complete.', uploadComplete: true, uploadStarted: true, uploadError: false })
logger.trace('creat participant', participant)
var pIota = {
//participant iota
parentId: props.parentId || (props._id && props._id.toString()), // a viewer with a human has no parentId, but a recorder has the viewer as it's parentId
subject: 'Participant:' + props.subject,
description: 'A participant in the following discussion:' + props.description,
component: {
component: 'MergeParticipants',
participant: participant,
},
}
if (props.bp_info) {
// don't cause the property to exist in the Iota if there is none.
pIota.component.participant.bp_info = Object.assign({}, props.bp_info) // make a copy cause we are going to delete stuff
delete pIota.component.participant.bp_info.campaign_email
delete pIota.component.participant.bp_info.personal_email
if (props.bp_info.candidate_name) pIota.component.participant.name = props.bp_info.candidate_name
}
window.socket.emit('create-participant', pIota, result => {
logger.trace('createParticipant participant created', result)
})
}
window.socket.emit('create-participant', pIota, result => {
logger.trace('createParticipant participant created', result)
})
}
}

var stream = ss.createStream()
stream.on('error', err => {
logger.error('createParticipant.upload socket stream error:', err)
})

var ssSocket = ss(window.socket)
//use this for debugging
//ssSocket._oldEmit = ssSocket.emit
//ssSocket.emit = ((...args) => (console.info("emit", ...args), ssSocket._oldEmit(...args)))
ssSocket.emit('stream-upload-video', stream, { name: file_name, size: blob.size }, responseUrl)
var stream = ss.createStream()
stream.on('error', err => {
logger.error('createParticipant.upload socket stream error:', err.message || err, "connected:", window.socket.connected)
eventError('There was an error uploading the video. See if you can try again')
})
var bstream = ss.createBlobReadStream(blob, { highWaterMark: 1024 * 200 }) // high hiwWaterMark to increase upload speed
bstream.on('error', err => {
logger.error('createParticipant.upload blob stream error:', err.message || err)
eventError(`There was an error uploading: ${err.message || err}`)
})
bstream.on('data', chunk => {
setTimeout(() => updateProgress(chunk.length)) // just to be safe don't do much within the pipe
})
bstream.pipe(stream)
ssSocket.emit('stream-upload-video', stream, { name: file_name, size: blob.size }, responseUrl);
}

var bstream = ss
.createBlobReadStream(blob, { highWaterMark: 1024 * 200 }) // high hiwWaterMark to increase upload speed
.pipe(
through2((chunk, enc, cb) => {
updateProgress(chunk)
cb(null, chunk) // 'this' becomes this of the react component rather than this of through2 - so pass the data back in the callback
})
)
.pipe(stream)
logger.info('createParticipant.onUserUpload')
//logger.trace('createParticipant.onUserUpload', props) // do not log props. If there are Blobs in there it will cause the transport websocket to disconnect

bstream.on('error', err => {
logger.error('createParticipant.upload blob stream error:', err)
})
stream.on('end', () => {
var uploadArgs
if ((uploadArgs = uploadQueue.shift())) {
return upload(...uploadArgs)
} else {
progressFunc && progressFunc({ progress: 'complete.', uploadComplete: true })
logger.trace('createParticipant upload after login complete')
}
})
}
for (let round = 0; round < adjustedSpeakingBlobs.length; round++) {
totalSize += adjustedSpeakingBlobs[round].size
uploadQueue.push([adjustedSpeakingBlobs[round], 'speaking', round])
}
if (human.listeningBlob) {
uploadQueue.push([human.listeningBlob, 'listening', 0])
totalSize += human.listeningBlob.size
}

logger.info('createParticipant.onUserUpload')
logger.trace('createParticipant.onUserUpload', props)
progressFunc?.({ progress: `${totalSize} to upload`, uploadComplete: false, uploadStarted: true, uploadError: false })

for (let round = 0; round < adjustedSpeakingBlobs.length; round++) {
totalSize += adjustedSpeakingBlobs[round].size
uploadQueue.push([adjustedSpeakingBlobs[round], 'speaking', round])
}
if (human.listeningBlob) {
uploadQueue.push([human.listeningBlob, 'listening', 0])
totalSize += human.listeningBlob.size
let uploadArgs
if ((uploadArgs = uploadQueue.shift())) {
upload(...uploadArgs)
}
}

let uploadArgs
if ((uploadArgs = uploadQueue.shift())) {
upload(...uploadArgs)
catch (error) {
if (window.socket.disconnected) window.socket.open() // some problems with the pipe would cause the stream to disconnect. It's fixed but lets leave this here.
logger.error("createParticipant caught error", message) // but it might not make it to the sever if the transport may be broke
try {
progressFunc?.({ progress: `upload failed. ${error.messaage || error}`, uploadComplete: false, uploadStarted: false, uploadError: true })
} catch (err) { } // if that doesn't work just continue
// then carry on
}
progressFunc && progressFunc({ progress: `${totalSize} to upload`, uploadComplete: false })
}
13 changes: 8 additions & 5 deletions app/socket-apis/stream-upload-video.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import cloudinary from 'cloudinary'

export default function streamUploadVideo(stream, data, cb) {
try {
logger.info("streamUploadVideo", data)
let self = this // an ss(socket)
const public_id = data.name.split('.')[0]
var cloudStream = cloudinary.v2.uploader.upload_stream(
Expand All @@ -25,6 +26,11 @@ export default function streamUploadVideo(stream, data, cb) {
}
}
)
logger.info("streamUploadVideo cloudStream opened")
cloudStream.on('error', err => {
logger.info('cloudStream error:', err)
cb()
})
stream.pipe(cloudStream).on('error', err => {
if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {
//socket-stream does not close correctly so we need to do it manually
Expand All @@ -34,12 +40,9 @@ export default function streamUploadVideo(stream, data, cb) {
} else logger.error('Error uploading stream:', public_id, err)
cb()
})
cloudStream.on('error', err => {
console.info('cloudStream error:', err)
cb()
})
logger.info("streamUploadVideo stream.pipe opened")
} catch (err) {
logger.error('caught error in streamUploadVideo, continuing')
logger.error('caught error in streamUploadVideo, continuing', err)
return
}
}
Loading