mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
MultiModal: addition of live recording...
This commit is contained in:
@@ -26,16 +26,7 @@ import {
|
|||||||
Typography
|
Typography
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { useTheme } from '@mui/material/styles'
|
import { useTheme } from '@mui/material/styles'
|
||||||
import {
|
import { IconDownload, IconSend, IconMicrophone, IconPhotoPlus, IconCircleDot } from '@tabler/icons'
|
||||||
IconDownload,
|
|
||||||
IconSend,
|
|
||||||
IconUpload,
|
|
||||||
IconMicrophone,
|
|
||||||
IconPhotoPlus,
|
|
||||||
IconPlayerStop,
|
|
||||||
IconPlayerRecord,
|
|
||||||
IconCircleDot
|
|
||||||
} from '@tabler/icons'
|
|
||||||
|
|
||||||
// project import
|
// project import
|
||||||
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
import { CodeBlock } from 'ui-component/markdown/CodeBlock'
|
||||||
@@ -59,6 +50,7 @@ import robotPNG from 'assets/images/robot.png'
|
|||||||
import userPNG from 'assets/images/account.png'
|
import userPNG from 'assets/images/account.png'
|
||||||
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from 'utils/genericHelper'
|
import { isValidURL, removeDuplicateURL, setLocalStorageChatflow } from 'utils/genericHelper'
|
||||||
import DeleteIcon from '@mui/icons-material/Delete'
|
import DeleteIcon from '@mui/icons-material/Delete'
|
||||||
|
import { cancelAudioRecording, startAudioRecording, stopAudioRecording } from './audio-recording'
|
||||||
|
|
||||||
export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@@ -84,11 +76,17 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
const getChatmessageApi = useApi(chatmessageApi.getInternalChatmessageFromChatflow)
|
||||||
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
const getIsChatflowStreamingApi = useApi(chatflowsApi.getIsChatflowStreaming)
|
||||||
|
|
||||||
|
// drag & drop and file input
|
||||||
const fileUploadRef = useRef(null)
|
const fileUploadRef = useRef(null)
|
||||||
const getAllowChatFlowUploads = useApi(chatflowsApi.getAllowChatflowUploads)
|
const getAllowChatFlowUploads = useApi(chatflowsApi.getAllowChatflowUploads)
|
||||||
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
|
const [isChatFlowAvailableForUploads, setIsChatFlowAvailableForUploads] = useState(false)
|
||||||
const [previews, setPreviews] = useState([])
|
const [previews, setPreviews] = useState([])
|
||||||
const [isDragOver, setIsDragOver] = useState(false)
|
const [isDragOver, setIsDragOver] = useState(false)
|
||||||
|
|
||||||
|
// recording
|
||||||
|
const [isRecording, setIsRecording] = useState(false)
|
||||||
|
const [recordingNotSupported, setRecordingNotSupported] = useState(false)
|
||||||
|
|
||||||
const handleDragOver = (e) => {
|
const handleDragOver = (e) => {
|
||||||
if (!isChatFlowAvailableForUploads) {
|
if (!isChatFlowAvailableForUploads) {
|
||||||
return
|
return
|
||||||
@@ -227,6 +225,24 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
event.target.value = null
|
event.target.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addRecordingToPreviews = (blob) => {
|
||||||
|
const mimeType = blob.type.substring(0, blob.type.indexOf(';'))
|
||||||
|
// read blob and add to previews
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.readAsDataURL(blob)
|
||||||
|
reader.onloadend = () => {
|
||||||
|
const base64data = reader.result
|
||||||
|
const upload = {
|
||||||
|
data: base64data,
|
||||||
|
preview: audioUploadSVG,
|
||||||
|
type: 'audio',
|
||||||
|
name: 'audio.wav',
|
||||||
|
mime: mimeType
|
||||||
|
}
|
||||||
|
setPreviews((prevPreviews) => [...prevPreviews, upload])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDragEnter = (e) => {
|
const handleDragEnter = (e) => {
|
||||||
if (isChatFlowAvailableForUploads) {
|
if (isChatFlowAvailableForUploads) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -271,6 +287,21 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
setPreviews([])
|
setPreviews([])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onMicrophonePressed = () => {
|
||||||
|
setIsRecording(true)
|
||||||
|
startAudioRecording(setIsRecording, setRecordingNotSupported)
|
||||||
|
}
|
||||||
|
const onRecordingCancelled = () => {
|
||||||
|
cancelAudioRecording()
|
||||||
|
setIsRecording(false)
|
||||||
|
setRecordingNotSupported(false)
|
||||||
|
}
|
||||||
|
const onRecordingStopped = () => {
|
||||||
|
stopAudioRecording(addRecordingToPreviews)
|
||||||
|
setIsRecording(false)
|
||||||
|
setRecordingNotSupported(false)
|
||||||
|
}
|
||||||
|
|
||||||
const onSourceDialogClick = (data, title) => {
|
const onSourceDialogClick = (data, title) => {
|
||||||
setSourceDialogProps({ data, title })
|
setSourceDialogProps({ data, title })
|
||||||
setSourceDialogOpen(true)
|
setSourceDialogOpen(true)
|
||||||
@@ -487,8 +518,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
getIsChatflowStreamingApi.request(chatflowid)
|
getIsChatflowStreamingApi.request(chatflowid)
|
||||||
getAllowChatFlowUploads.request(chatflowid)
|
getAllowChatFlowUploads.request(chatflowid)
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
initAudioRecording()
|
setIsRecording(false)
|
||||||
|
|
||||||
socket = socketIOClient(baseURL)
|
socket = socketIOClient(baseURL)
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
@@ -530,39 +560,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
className={`file-drop-field`}
|
className={`file-drop-field`}
|
||||||
>
|
>
|
||||||
<div className={'audio-recording-container'}>
|
|
||||||
<div className='recording-control-buttons-container hide'>
|
|
||||||
<i className='cancel-recording-button' aria-hidden='true'>
|
|
||||||
<IconPlayerRecord />
|
|
||||||
</i>
|
|
||||||
<div className='recording-elapsed-time'>
|
|
||||||
<i className='red-recording-dot' aria-hidden='true'>
|
|
||||||
<IconCircleDot />
|
|
||||||
</i>
|
|
||||||
<p className='elapsed-time'></p>
|
|
||||||
</div>
|
|
||||||
<i className='stop-recording-button' aria-hidden='true'>
|
|
||||||
<IconPlayerStop />
|
|
||||||
</i>
|
|
||||||
</div>
|
|
||||||
<div className='text-indication-of-audio-playing-container'>
|
|
||||||
<p className='text-indication-of-audio-playing hide'>
|
|
||||||
Audio is playing<span>.</span>
|
|
||||||
<span>.</span>
|
|
||||||
<span>.</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='overlay hide'>
|
|
||||||
<div className='browser-not-supporting-audio-recording-box'>
|
|
||||||
<p>To record audio, use browsers like Chrome and Firefox that support audio recording.</p>
|
|
||||||
<button type='button' className='close-browser-not-supported-box'>
|
|
||||||
Ok.
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
|
||||||
<audio controls className='audio-element hide'></audio>
|
|
||||||
{isDragOver && getAllowChatFlowUploads.data?.allowUploads && (
|
{isDragOver && getAllowChatFlowUploads.data?.allowUploads && (
|
||||||
<Box className='drop-overlay'>
|
<Box className='drop-overlay'>
|
||||||
<Typography variant='h2'>Drop here to upload</Typography>
|
<Typography variant='h2'>Drop here to upload</Typography>
|
||||||
@@ -576,6 +573,41 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
{isRecording && (
|
||||||
|
<Box className='drop-overlay'>
|
||||||
|
<div className={'audio-recording-container'}>
|
||||||
|
<Typography variant='h2'>Recording</Typography>
|
||||||
|
<div className='recording-control-buttons-container'>
|
||||||
|
<i className='cancel-recording-button'>
|
||||||
|
<Button variant='outlined' color='error' onClick={onRecordingCancelled}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</i>
|
||||||
|
<div className='recording-elapsed-time'>
|
||||||
|
<i className='red-recording-dot'>
|
||||||
|
<IconCircleDot />
|
||||||
|
</i>
|
||||||
|
<p id='elapsed-time'>00:00</p>
|
||||||
|
</div>
|
||||||
|
<i className='stop-recording-button'>
|
||||||
|
<Button variant='outlined' color='primary' onClick={onRecordingStopped}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{recordingNotSupported && (
|
||||||
|
<div className='overlay hide'>
|
||||||
|
<div className='browser-not-supporting-audio-recording-box'>
|
||||||
|
<p>To record audio, use browsers like Chrome and Firefox that support audio recording.</p>
|
||||||
|
<button type='button' onClick={() => onRecordingCancelled()}>
|
||||||
|
Ok.
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
<div className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
|
<div className={`${isDialog ? 'cloud-dialog' : 'cloud'}`}>
|
||||||
<div ref={ps} id='messagelist' className={'messagelist'}>
|
<div ref={ps} id='messagelist' className={'messagelist'}>
|
||||||
{messages &&
|
{messages &&
|
||||||
@@ -804,9 +836,15 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
endAdornment={
|
endAdornment={
|
||||||
<>
|
<>
|
||||||
{isChatFlowAvailableForUploads && (
|
{isChatFlowAvailableForUploads && (
|
||||||
<InputAdornment className={'start-recording-button'} position='end'>
|
<InputAdornment position='end'>
|
||||||
<IconButton type='button' disabled={loading || !chatflowid} edge='end'>
|
<IconButton
|
||||||
|
onClick={() => onMicrophonePressed()}
|
||||||
|
type='button'
|
||||||
|
disabled={loading || !chatflowid}
|
||||||
|
edge='end'
|
||||||
|
>
|
||||||
<IconMicrophone
|
<IconMicrophone
|
||||||
|
className={'start-recording-button'}
|
||||||
color={
|
color={
|
||||||
loading || !chatflowid ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'
|
loading || !chatflowid ? '#9e9e9e' : customization.isDarkMode ? 'white' : '#1e88e5'
|
||||||
}
|
}
|
||||||
@@ -839,7 +877,6 @@ export const ChatMessage = ({ open, chatflowid, isDialog }) => {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
<SourceDocDialog show={sourceDialogOpen} dialogProps={sourceDialogProps} onCancel={() => setSourceDialogOpen(false)} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -850,449 +887,3 @@ ChatMessage.propTypes = {
|
|||||||
chatflowid: PropTypes.string,
|
chatflowid: PropTypes.string,
|
||||||
isDialog: PropTypes.bool
|
isDialog: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// audio-recording.js ---------------
|
|
||||||
//View
|
|
||||||
let microphoneButton = document.getElementsByClassName('start-recording-button')[0]
|
|
||||||
let recordingControlButtonsContainer = document.getElementsByClassName('recording-control-buttons-container')[0]
|
|
||||||
let stopRecordingButton = document.getElementsByClassName('stop-recording-button')[0]
|
|
||||||
let cancelRecordingButton = document.getElementsByClassName('cancel-recording-button')[0]
|
|
||||||
let elapsedTimeTag = document.getElementsByClassName('elapsed-time')[0]
|
|
||||||
let closeBrowserNotSupportedBoxButton = document.getElementsByClassName('close-browser-not-supported-box')[0]
|
|
||||||
let overlay = document.getElementsByClassName('overlay')[0]
|
|
||||||
let audioElement = document.getElementsByClassName('audio-element')[0]
|
|
||||||
let audioElementSource = audioElement?.getElementsByTagName('source')[0]
|
|
||||||
let textIndicatorOfAudiPlaying = document.getElementsByClassName('text-indication-of-audio-playing')[0]
|
|
||||||
|
|
||||||
const initAudioRecording = () => {
|
|
||||||
microphoneButton = document.getElementsByClassName('start-recording-button')[0]
|
|
||||||
recordingControlButtonsContainer = document.getElementsByClassName('recording-control-buttons-container')[0]
|
|
||||||
stopRecordingButton = document.getElementsByClassName('stop-recording-button')[0]
|
|
||||||
cancelRecordingButton = document.getElementsByClassName('cancel-recording-button')[0]
|
|
||||||
elapsedTimeTag = document.getElementsByClassName('elapsed-time')[0]
|
|
||||||
closeBrowserNotSupportedBoxButton = document.getElementsByClassName('close-browser-not-supported-box')[0]
|
|
||||||
overlay = document.getElementsByClassName('overlay')[0]
|
|
||||||
audioElement = document.getElementsByClassName('audio-element')[0]
|
|
||||||
audioElementSource = audioElement?.getElementsByTagName('source')[0]
|
|
||||||
textIndicatorOfAudiPlaying = document.getElementsByClassName('text-indication-of-audio-playing')[0]
|
|
||||||
//Listeners
|
|
||||||
|
|
||||||
//Listen to start recording button
|
|
||||||
if (microphoneButton) microphoneButton.onclick = startAudioRecording
|
|
||||||
|
|
||||||
//Listen to stop recording button
|
|
||||||
if (stopRecordingButton) stopRecordingButton.onclick = stopAudioRecording
|
|
||||||
|
|
||||||
//Listen to cancel recording button
|
|
||||||
if (cancelRecordingButton) cancelRecordingButton.onclick = cancelAudioRecording
|
|
||||||
|
|
||||||
//Listen to when the ok button is clicked in the browser not supporting audio recording box
|
|
||||||
if (closeBrowserNotSupportedBoxButton) closeBrowserNotSupportedBoxButton.onclick = hideBrowserNotSupportedOverlay
|
|
||||||
|
|
||||||
//Listen to when the audio being played ends
|
|
||||||
if (audioElement) audioElement.onended = hideTextIndicatorOfAudioPlaying
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Displays recording control buttons */
|
|
||||||
function handleDisplayingRecordingControlButtons() {
|
|
||||||
//Hide the microphone button that starts audio recording
|
|
||||||
microphoneButton.style.display = 'none'
|
|
||||||
|
|
||||||
//Display the recording control buttons
|
|
||||||
recordingControlButtonsContainer.classList.remove('hide')
|
|
||||||
|
|
||||||
//Handle the displaying of the elapsed recording time
|
|
||||||
handleElapsedRecordingTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Hide the displayed recording control buttons */
|
|
||||||
function handleHidingRecordingControlButtons() {
|
|
||||||
//Display the microphone button that starts audio recording
|
|
||||||
microphoneButton.style.display = 'block'
|
|
||||||
|
|
||||||
//Hide the recording control buttons
|
|
||||||
recordingControlButtonsContainer.classList.add('hide')
|
|
||||||
|
|
||||||
//stop interval that handles both time elapsed and the red dot
|
|
||||||
clearInterval(elapsedTimeTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Displays browser not supported info box for the user*/
|
|
||||||
function displayBrowserNotSupportedOverlay() {
|
|
||||||
overlay.classList.remove('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Displays browser not supported info box for the user*/
|
|
||||||
function hideBrowserNotSupportedOverlay() {
|
|
||||||
overlay.classList.add('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates a source element for the audio element in the HTML document*/
|
|
||||||
function createSourceForAudioElement() {
|
|
||||||
let sourceElement = document.createElement('source')
|
|
||||||
audioElement.appendChild(sourceElement)
|
|
||||||
|
|
||||||
audioElementSource = sourceElement
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Display the text indicator of the audio being playing in the background */
|
|
||||||
function displayTextIndicatorOfAudioPlaying() {
|
|
||||||
textIndicatorOfAudiPlaying.classList.remove('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Hide the text indicator of the audio being playing in the background */
|
|
||||||
function hideTextIndicatorOfAudioPlaying() {
|
|
||||||
textIndicatorOfAudiPlaying.classList.add('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
//Controller
|
|
||||||
|
|
||||||
/** Stores the actual start time when an audio recording begins to take place to ensure elapsed time start time is accurate*/
|
|
||||||
let audioRecordStartTime
|
|
||||||
|
|
||||||
/** Stores the maximum recording time in hours to stop recording once maximum recording hour has been reached */
|
|
||||||
let maximumRecordingTimeInHours = 1
|
|
||||||
|
|
||||||
/** Stores the reference of the setInterval function that controls the timer in audio recording*/
|
|
||||||
let elapsedTimeTimer
|
|
||||||
|
|
||||||
/** Starts the audio recording*/
|
|
||||||
function startAudioRecording() {
|
|
||||||
console.log('Recording Audio...')
|
|
||||||
|
|
||||||
//If a previous audio recording is playing, pause it
|
|
||||||
let recorderAudioIsPlaying = !audioElement.paused // the paused property tells whether the media element is paused or not
|
|
||||||
console.log('paused?', !recorderAudioIsPlaying)
|
|
||||||
if (recorderAudioIsPlaying) {
|
|
||||||
audioElement.pause()
|
|
||||||
//also hide the audio playing indicator displayed on the screen
|
|
||||||
hideTextIndicatorOfAudioPlaying()
|
|
||||||
}
|
|
||||||
|
|
||||||
//start recording using the audio recording API
|
|
||||||
audioRecorder
|
|
||||||
.start()
|
|
||||||
.then(() => {
|
|
||||||
//on success
|
|
||||||
|
|
||||||
//store the recording start time to display the elapsed time according to it
|
|
||||||
audioRecordStartTime = new Date()
|
|
||||||
|
|
||||||
//display control buttons to offer the functionality of stop and cancel
|
|
||||||
handleDisplayingRecordingControlButtons()
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
//on error
|
|
||||||
//No Browser Support Error
|
|
||||||
if (error.message.includes('mediaDevices API or getUserMedia method is not supported in this browser.')) {
|
|
||||||
console.log('To record audio, use browsers like Chrome and Firefox.')
|
|
||||||
displayBrowserNotSupportedOverlay()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Error handling structure
|
|
||||||
switch (error.name) {
|
|
||||||
case 'AbortError': //error from navigator.mediaDevices.getUserMedia
|
|
||||||
console.log('An AbortError has occurred.')
|
|
||||||
break
|
|
||||||
case 'NotAllowedError': //error from navigator.mediaDevices.getUserMedia
|
|
||||||
console.log('A NotAllowedError has occurred. User might have denied permission.')
|
|
||||||
break
|
|
||||||
case 'NotFoundError': //error from navigator.mediaDevices.getUserMedia
|
|
||||||
console.log('A NotFoundError has occurred.')
|
|
||||||
break
|
|
||||||
case 'NotReadableError': //error from navigator.mediaDevices.getUserMedia
|
|
||||||
console.log('A NotReadableError has occurred.')
|
|
||||||
break
|
|
||||||
case 'SecurityError': //error from navigator.mediaDevices.getUserMedia or from the MediaRecorder.start
|
|
||||||
console.log('A SecurityError has occurred.')
|
|
||||||
break
|
|
||||||
case 'TypeError': //error from navigator.mediaDevices.getUserMedia
|
|
||||||
console.log('A TypeError has occurred.')
|
|
||||||
break
|
|
||||||
case 'InvalidStateError': //error from the MediaRecorder.start
|
|
||||||
console.log('An InvalidStateError has occurred.')
|
|
||||||
break
|
|
||||||
case 'UnknownError': //error from the MediaRecorder.start
|
|
||||||
console.log('An UnknownError has occurred.')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
console.log('An error occurred with the error name ' + error.name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/** Stop the currently started audio recording & sends it
|
|
||||||
*/
|
|
||||||
function stopAudioRecording() {
|
|
||||||
console.log('Stopping Audio Recording...')
|
|
||||||
|
|
||||||
//stop the recording using the audio recording API
|
|
||||||
audioRecorder
|
|
||||||
.stop()
|
|
||||||
.then((audioAsblob) => {
|
|
||||||
//Play recorder audio
|
|
||||||
playAudio(audioAsblob)
|
|
||||||
|
|
||||||
//hide recording control button & return record icon
|
|
||||||
handleHidingRecordingControlButtons()
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
//Error handling structure
|
|
||||||
switch (error.name) {
|
|
||||||
case 'InvalidStateError': //error from the MediaRecorder.stop
|
|
||||||
console.log('An InvalidStateError has occurred.')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
console.log('An error occurred with the error name ' + error.name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Cancel the currently started audio recording */
|
|
||||||
function cancelAudioRecording() {
|
|
||||||
console.log('Canceling audio...')
|
|
||||||
|
|
||||||
//cancel the recording using the audio recording API
|
|
||||||
audioRecorder.cancel()
|
|
||||||
|
|
||||||
//hide recording control button & return record icon
|
|
||||||
handleHidingRecordingControlButtons()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Plays recorded audio using the audio element in the HTML document
|
|
||||||
* @param {Blob} recorderAudioAsBlob - recorded audio as a Blob Object
|
|
||||||
*/
|
|
||||||
function playAudio(recorderAudioAsBlob) {
|
|
||||||
//read content of files (Blobs) asynchronously
|
|
||||||
let reader = new FileReader()
|
|
||||||
|
|
||||||
//once content has been read
|
|
||||||
reader.onload = (e) => {
|
|
||||||
//store the base64 URL that represents the URL of the recording audio
|
|
||||||
let base64URL = e.target.result
|
|
||||||
|
|
||||||
//If this is the first audio playing, create a source element
|
|
||||||
//as pre-populating the HTML with a source of empty src causes error
|
|
||||||
if (!audioElementSource)
|
|
||||||
//if it is not defined create it (happens first time only)
|
|
||||||
createSourceForAudioElement()
|
|
||||||
|
|
||||||
//set the audio element's source using the base64 URL
|
|
||||||
audioElementSource.src = base64URL
|
|
||||||
|
|
||||||
//set the type of the audio element based on the recorded audio's Blob type
|
|
||||||
let BlobType = recorderAudioAsBlob.type.includes(';')
|
|
||||||
? recorderAudioAsBlob.type.substr(0, recorderAudioAsBlob.type.indexOf(';'))
|
|
||||||
: recorderAudioAsBlob.type
|
|
||||||
audioElementSource.type = BlobType
|
|
||||||
|
|
||||||
//call the load method as it is used to update the audio element after changing the source or other settings
|
|
||||||
audioElement.load()
|
|
||||||
|
|
||||||
//play the audio after successfully setting new src and type that corresponds to the recorded audio
|
|
||||||
console.log('Playing audio...')
|
|
||||||
audioElement.play()
|
|
||||||
|
|
||||||
//Display text indicator of having the audio play in the background
|
|
||||||
displayTextIndicatorOfAudioPlaying()
|
|
||||||
}
|
|
||||||
|
|
||||||
//read content and convert it to a URL (base64)
|
|
||||||
reader.readAsDataURL(recorderAudioAsBlob)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Computes the elapsed recording time since the moment the function is called in the format h:m:s*/
|
|
||||||
function handleElapsedRecordingTime() {
|
|
||||||
//display initial time when recording begins
|
|
||||||
displayElapsedTimeDuringAudioRecording('00:00')
|
|
||||||
|
|
||||||
//create an interval that compute & displays elapsed time, as well as, animate red dot - every second
|
|
||||||
elapsedTimeTimer = setInterval(() => {
|
|
||||||
//compute the elapsed time every second
|
|
||||||
let elapsedTime = computeElapsedTime(audioRecordStartTime) //pass the actual record start time
|
|
||||||
//display the elapsed time
|
|
||||||
displayElapsedTimeDuringAudioRecording(elapsedTime)
|
|
||||||
}, 1000) //every second
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Display elapsed time during audio recording
|
|
||||||
* @param {String} elapsedTime - elapsed time in the format mm:ss or hh:mm:ss
|
|
||||||
*/
|
|
||||||
function displayElapsedTimeDuringAudioRecording(elapsedTime) {
|
|
||||||
//1. display the passed elapsed time as the elapsed time in the elapsedTime HTML element
|
|
||||||
elapsedTimeTag.innerHTML = elapsedTime
|
|
||||||
|
|
||||||
//2. Stop the recording when the max number of hours is reached
|
|
||||||
if (elapsedTimeReachedMaximumNumberOfHours(elapsedTime)) {
|
|
||||||
stopAudioRecording()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {String} elapsedTime - elapsed time in the format mm:ss or hh:mm:ss
|
|
||||||
* @returns {Boolean} whether the elapsed time reached the maximum number of hours or not
|
|
||||||
*/
|
|
||||||
function elapsedTimeReachedMaximumNumberOfHours(elapsedTime) {
|
|
||||||
//Split the elapsed time by the symbol that separates the hours, minutes and seconds :
|
|
||||||
let elapsedTimeSplit = elapsedTime.split(':')
|
|
||||||
|
|
||||||
//Turn the maximum recording time in hours to a string and pad it with zero if less than 10
|
|
||||||
let maximumRecordingTimeInHoursAsString =
|
|
||||||
maximumRecordingTimeInHours < 10 ? '0' + maximumRecordingTimeInHours : maximumRecordingTimeInHours.toString()
|
|
||||||
|
|
||||||
//if the elapsed time reach hours and also reach the maximum recording time in hours return true
|
|
||||||
if (elapsedTimeSplit.length === 3 && elapsedTimeSplit[0] === maximumRecordingTimeInHoursAsString) return true
|
|
||||||
//otherwise, return false
|
|
||||||
else return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Computes the elapsedTime since the moment the function is called in the format mm:ss or hh:mm:ss
|
|
||||||
* @param {String} startTime - start time to compute the elapsed time since
|
|
||||||
* @returns {String} elapsed time in mm:ss format or hh:mm:ss format, if elapsed hours are 0.
|
|
||||||
*/
|
|
||||||
function computeElapsedTime(startTime) {
|
|
||||||
//record end time
|
|
||||||
let endTime = new Date()
|
|
||||||
|
|
||||||
//time difference in ms
|
|
||||||
let timeDiff = endTime - startTime
|
|
||||||
|
|
||||||
//convert time difference from ms to seconds
|
|
||||||
timeDiff = timeDiff / 1000
|
|
||||||
|
|
||||||
//extract integer seconds that don't form a minute using %
|
|
||||||
let seconds = Math.floor(timeDiff % 60) //ignoring incomplete seconds (floor)
|
|
||||||
|
|
||||||
//pad seconds with a zero if necessary
|
|
||||||
seconds = seconds < 10 ? '0' + seconds : seconds
|
|
||||||
|
|
||||||
//convert time difference from seconds to minutes using %
|
|
||||||
timeDiff = Math.floor(timeDiff / 60)
|
|
||||||
|
|
||||||
//extract integer minutes that don't form an hour using %
|
|
||||||
let minutes = timeDiff % 60 //no need to floor possible incomplete minutes, because they've been handled as seconds
|
|
||||||
minutes = minutes < 10 ? '0' + minutes : minutes
|
|
||||||
|
|
||||||
//convert time difference from minutes to hours
|
|
||||||
timeDiff = Math.floor(timeDiff / 60)
|
|
||||||
|
|
||||||
//extract integer hours that don't form a day using %
|
|
||||||
let hours = timeDiff % 24 //no need to floor possible incomplete hours, because they've been handled as seconds
|
|
||||||
|
|
||||||
//convert time difference from hours to days
|
|
||||||
timeDiff = Math.floor(timeDiff / 24)
|
|
||||||
|
|
||||||
// the rest of timeDiff is number of days
|
|
||||||
let days = timeDiff //add days to hours
|
|
||||||
|
|
||||||
let totalHours = hours + days * 24
|
|
||||||
totalHours = totalHours < 10 ? '0' + totalHours : totalHours
|
|
||||||
|
|
||||||
if (totalHours === '00') {
|
|
||||||
return minutes + ':' + seconds
|
|
||||||
} else {
|
|
||||||
return totalHours + ':' + minutes + ':' + seconds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//API to handle audio recording
|
|
||||||
|
|
||||||
const audioRecorder = {
|
|
||||||
/** Stores the recorded audio as Blob objects of audio data as the recording continues*/
|
|
||||||
audioBlobs: [] /*of type Blob[]*/,
|
|
||||||
/** Stores the reference of the MediaRecorder instance that handles the MediaStream when recording starts*/
|
|
||||||
mediaRecorder: null /*of type MediaRecorder*/,
|
|
||||||
/** Stores the reference to the stream currently capturing the audio*/
|
|
||||||
streamBeingCaptured: null /*of type MediaStream*/,
|
|
||||||
/** Start recording the audio
|
|
||||||
* @returns {Promise} - returns a promise that resolves if audio recording successfully started
|
|
||||||
*/
|
|
||||||
start: function () {
|
|
||||||
//Feature Detection
|
|
||||||
if (!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)) {
|
|
||||||
//Feature is not supported in browser
|
|
||||||
//return a custom error
|
|
||||||
return Promise.reject(new Error('mediaDevices API or getUserMedia method is not supported in this browser.'))
|
|
||||||
} else {
|
|
||||||
//Feature is supported in browser
|
|
||||||
|
|
||||||
//create an audio stream
|
|
||||||
return (
|
|
||||||
navigator.mediaDevices
|
|
||||||
.getUserMedia({ audio: true } /*of type MediaStreamConstraints*/)
|
|
||||||
//returns a promise that resolves to the audio stream
|
|
||||||
.then((stream) /*of type MediaStream*/ => {
|
|
||||||
//save the reference of the stream to be able to stop it when necessary
|
|
||||||
audioRecorder.streamBeingCaptured = stream
|
|
||||||
|
|
||||||
//create a media recorder instance by passing that stream into the MediaRecorder constructor
|
|
||||||
audioRecorder.mediaRecorder = new MediaRecorder(stream) /*the MediaRecorder interface of the MediaStream Recording
|
|
||||||
API provides functionality to easily record media*/
|
|
||||||
|
|
||||||
//clear previously saved audio Blobs, if any
|
|
||||||
audioRecorder.audioBlobs = []
|
|
||||||
|
|
||||||
//add a dataavailable event listener in order to store the audio data Blobs when recording
|
|
||||||
audioRecorder.mediaRecorder.addEventListener('dataavailable', (event) => {
|
|
||||||
//store audio Blob object
|
|
||||||
audioRecorder.audioBlobs.push(event.data)
|
|
||||||
})
|
|
||||||
|
|
||||||
//start the recording by calling the start method on the media recorder
|
|
||||||
audioRecorder.mediaRecorder.start()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
/* errors are not handled in the API because if its handled and the promise is chained, the .then after the catch will be executed*/
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/** Stop the started audio recording
|
|
||||||
* @returns {Promise} - returns a promise that resolves to the audio as a blob file
|
|
||||||
*/
|
|
||||||
stop: function () {
|
|
||||||
//return a promise that would return the blob or URL of the recording
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
//save audio type to pass to set the Blob type
|
|
||||||
let mimeType = audioRecorder.mediaRecorder.mimeType
|
|
||||||
|
|
||||||
//listen to the stop event in order to create & return a single Blob object
|
|
||||||
audioRecorder.mediaRecorder.addEventListener('stop', () => {
|
|
||||||
//create a single blob object, as we might have gathered a few Blob objects that needs to be joined as one
|
|
||||||
let audioBlob = new Blob(audioRecorder.audioBlobs, { type: mimeType })
|
|
||||||
|
|
||||||
//resolve promise with the single audio blob representing the recorded audio
|
|
||||||
resolve(audioBlob)
|
|
||||||
})
|
|
||||||
audioRecorder.cancel()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/** Cancel audio recording*/
|
|
||||||
cancel: function () {
|
|
||||||
//stop the recording feature
|
|
||||||
audioRecorder.mediaRecorder.stop()
|
|
||||||
|
|
||||||
//stop all the tracks on the active stream in order to stop the stream
|
|
||||||
audioRecorder.stopStream()
|
|
||||||
|
|
||||||
//reset API properties for next recording
|
|
||||||
audioRecorder.resetRecordingProperties()
|
|
||||||
},
|
|
||||||
/** Stop all the tracks on the active stream in order to stop the stream and remove
|
|
||||||
* the red flashing dot showing in the tab
|
|
||||||
*/
|
|
||||||
stopStream: function () {
|
|
||||||
//stopping the capturing request by stopping all the tracks on the active stream
|
|
||||||
audioRecorder.streamBeingCaptured
|
|
||||||
.getTracks() //get all tracks from the stream
|
|
||||||
.forEach((track) /*of type MediaStreamTrack*/ => track.stop()) //stop each one
|
|
||||||
},
|
|
||||||
/** Reset all the recording properties including the media recorder and stream being captured*/
|
|
||||||
resetRecordingProperties: function () {
|
|
||||||
audioRecorder.mediaRecorder = null
|
|
||||||
audioRecorder.streamBeingCaptured = null
|
|
||||||
|
|
||||||
/*No need to remove event listeners attached to mediaRecorder as
|
|
||||||
If a DOM element which is removed is reference-free (no references pointing to it), the element itself is picked
|
|
||||||
up by the garbage collector as well as any event handlers/listeners associated with it.
|
|
||||||
getEventListeners(audioRecorder.mediaRecorder) will return an empty array of events.*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
/*horizontal centering*/
|
/*horizontal centering*/
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
.start-recording-button {
|
.start-recording-button {
|
||||||
font-size: 70px;
|
font-size: 70px;
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 334px;
|
width: 334px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
.cancel-recording-button,
|
.cancel-recording-button,
|
||||||
.stop-recording-button {
|
.stop-recording-button {
|
||||||
@@ -61,6 +63,7 @@
|
|||||||
color: #27a527;
|
color: #27a527;
|
||||||
}
|
}
|
||||||
.recording-elapsed-time {
|
.recording-elapsed-time {
|
||||||
|
font-size: 32px;
|
||||||
/*targeting Chrome & Safari*/
|
/*targeting Chrome & Safari*/
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
/*targeting IE10*/
|
/*targeting IE10*/
|
||||||
|
|||||||
@@ -1,41 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview This file contains the API to handle audio recording.
|
||||||
|
* Originally from 'https://ralzohairi.medium.com/audio-recording-in-javascript-96eed45b75ee'
|
||||||
|
*/
|
||||||
|
|
||||||
// audio-recording.js ---------------
|
// audio-recording.js ---------------
|
||||||
//View
|
let microphoneButton, elapsedTimeTag
|
||||||
let microphoneButton = document.getElementsByClassName('start-recording-button')[0]
|
|
||||||
let recordingControlButtonsContainer = document.getElementsByClassName('recording-control-buttons-container')[0]
|
|
||||||
let stopRecordingButton = document.getElementsByClassName('stop-recording-button')[0]
|
|
||||||
let cancelRecordingButton = document.getElementsByClassName('cancel-recording-button')[0]
|
|
||||||
let elapsedTimeTag = document.getElementsByClassName('elapsed-time')[0]
|
|
||||||
let closeBrowserNotSupportedBoxButton = document.getElementsByClassName('close-browser-not-supported-box')[0]
|
|
||||||
let overlay = document.getElementsByClassName('overlay')[0]
|
|
||||||
let audioElement = document.getElementsByClassName('audio-element')[0]
|
|
||||||
let audioElementSource = document.getElementsByClassName('audio-element')[0].getElementsByTagName('source')[0]
|
|
||||||
let textIndicatorOfAudiPlaying = document.getElementsByClassName('text-indication-of-audio-playing')[0]
|
|
||||||
|
|
||||||
//Listeners
|
/** Initialize controls */
|
||||||
|
function initializeControls() {
|
||||||
//Listen to start recording button
|
microphoneButton = document.getElementsByClassName('start-recording-button')[0]
|
||||||
microphoneButton.onclick = startAudioRecording
|
}
|
||||||
|
|
||||||
//Listen to stop recording button
|
|
||||||
stopRecordingButton.onclick = stopAudioRecording
|
|
||||||
|
|
||||||
//Listen to cancel recording button
|
|
||||||
cancelRecordingButton.onclick = cancelAudioRecording
|
|
||||||
|
|
||||||
//Listen to when the ok button is clicked in the browser not supporting audio recording box
|
|
||||||
closeBrowserNotSupportedBoxButton.onclick = hideBrowserNotSupportedOverlay
|
|
||||||
|
|
||||||
//Listen to when the audio being played ends
|
|
||||||
audioElement.onended = hideTextIndicatorOfAudioPlaying
|
|
||||||
|
|
||||||
/** Displays recording control buttons */
|
/** Displays recording control buttons */
|
||||||
function handleDisplayingRecordingControlButtons() {
|
function handleDisplayingRecordingControlButtons() {
|
||||||
//Hide the microphone button that starts audio recording
|
//Hide the microphone button that starts audio recording
|
||||||
microphoneButton.style.display = 'none'
|
microphoneButton.style.display = 'none'
|
||||||
|
|
||||||
//Display the recording control buttons
|
|
||||||
recordingControlButtonsContainer.classList.remove('hide')
|
|
||||||
|
|
||||||
//Handle the displaying of the elapsed recording time
|
//Handle the displaying of the elapsed recording time
|
||||||
handleElapsedRecordingTime()
|
handleElapsedRecordingTime()
|
||||||
}
|
}
|
||||||
@@ -45,43 +25,10 @@ function handleHidingRecordingControlButtons() {
|
|||||||
//Display the microphone button that starts audio recording
|
//Display the microphone button that starts audio recording
|
||||||
microphoneButton.style.display = 'block'
|
microphoneButton.style.display = 'block'
|
||||||
|
|
||||||
//Hide the recording control buttons
|
|
||||||
recordingControlButtonsContainer.classList.add('hide')
|
|
||||||
|
|
||||||
//stop interval that handles both time elapsed and the red dot
|
//stop interval that handles both time elapsed and the red dot
|
||||||
clearInterval(elapsedTimeTimer)
|
clearInterval(elapsedTimeTimer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays browser not supported info box for the user*/
|
|
||||||
function displayBrowserNotSupportedOverlay() {
|
|
||||||
overlay.classList.remove('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Displays browser not supported info box for the user*/
|
|
||||||
function hideBrowserNotSupportedOverlay() {
|
|
||||||
overlay.classList.add('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates a source element for the audio element in the HTML document*/
|
|
||||||
function createSourceForAudioElement() {
|
|
||||||
let sourceElement = document.createElement('source')
|
|
||||||
audioElement.appendChild(sourceElement)
|
|
||||||
|
|
||||||
audioElementSource = sourceElement
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Display the text indicator of the audio being playing in the background */
|
|
||||||
function displayTextIndicatorOfAudioPlaying() {
|
|
||||||
textIndicatorOfAudiPlaying.classList.remove('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Hide the text indicator of the audio being playing in the background */
|
|
||||||
function hideTextIndicatorOfAudioPlaying() {
|
|
||||||
textIndicatorOfAudiPlaying.classList.add('hide')
|
|
||||||
}
|
|
||||||
|
|
||||||
//Controller
|
|
||||||
|
|
||||||
/** Stores the actual start time when an audio recording begins to take place to ensure elapsed time start time is accurate*/
|
/** Stores the actual start time when an audio recording begins to take place to ensure elapsed time start time is accurate*/
|
||||||
let audioRecordStartTime
|
let audioRecordStartTime
|
||||||
|
|
||||||
@@ -92,24 +39,17 @@ let maximumRecordingTimeInHours = 1
|
|||||||
let elapsedTimeTimer
|
let elapsedTimeTimer
|
||||||
|
|
||||||
/** Starts the audio recording*/
|
/** Starts the audio recording*/
|
||||||
function startAudioRecording() {
|
export function startAudioRecording(onRecordingStart, onUnsupportedBrowser) {
|
||||||
console.log('Recording Audio...')
|
initializeControls()
|
||||||
|
|
||||||
//If a previous audio recording is playing, pause it
|
|
||||||
let recorderAudioIsPlaying = !audioElement.paused // the paused property tells whether the media element is paused or not
|
|
||||||
console.log('paused?', !recorderAudioIsPlaying)
|
|
||||||
if (recorderAudioIsPlaying) {
|
|
||||||
audioElement.pause()
|
|
||||||
//also hide the audio playing indicator displayed on the screen
|
|
||||||
hideTextIndicatorOfAudioPlaying()
|
|
||||||
}
|
|
||||||
|
|
||||||
//start recording using the audio recording API
|
//start recording using the audio recording API
|
||||||
audioRecorder
|
audioRecorder
|
||||||
.start()
|
.start()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
//on success
|
//on success show the controls to stop and cancel the recording
|
||||||
|
if (onRecordingStart) {
|
||||||
|
onRecordingStart(true)
|
||||||
|
}
|
||||||
//store the recording start time to display the elapsed time according to it
|
//store the recording start time to display the elapsed time according to it
|
||||||
audioRecordStartTime = new Date()
|
audioRecordStartTime = new Date()
|
||||||
|
|
||||||
@@ -120,8 +60,9 @@ function startAudioRecording() {
|
|||||||
//on error
|
//on error
|
||||||
//No Browser Support Error
|
//No Browser Support Error
|
||||||
if (error.message.includes('mediaDevices API or getUserMedia method is not supported in this browser.')) {
|
if (error.message.includes('mediaDevices API or getUserMedia method is not supported in this browser.')) {
|
||||||
console.log('To record audio, use browsers like Chrome and Firefox.')
|
if (onUnsupportedBrowser) {
|
||||||
displayBrowserNotSupportedOverlay()
|
onUnsupportedBrowser(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Error handling structure
|
//Error handling structure
|
||||||
@@ -157,18 +98,16 @@ function startAudioRecording() {
|
|||||||
}
|
}
|
||||||
/** Stop the currently started audio recording & sends it
|
/** Stop the currently started audio recording & sends it
|
||||||
*/
|
*/
|
||||||
function stopAudioRecording() {
|
export function stopAudioRecording(addRecordingToPreviews) {
|
||||||
console.log('Stopping Audio Recording...')
|
|
||||||
|
|
||||||
//stop the recording using the audio recording API
|
//stop the recording using the audio recording API
|
||||||
audioRecorder
|
audioRecorder
|
||||||
.stop()
|
.stop()
|
||||||
.then((audioAsblob) => {
|
.then((audioBlob) => {
|
||||||
//Play recorder audio
|
|
||||||
playAudio(audioAsblob)
|
|
||||||
|
|
||||||
//hide recording control button & return record icon
|
//hide recording control button & return record icon
|
||||||
handleHidingRecordingControlButtons()
|
handleHidingRecordingControlButtons()
|
||||||
|
if (addRecordingToPreviews) {
|
||||||
|
addRecordingToPreviews(audioBlob)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
//Error handling structure
|
//Error handling structure
|
||||||
@@ -183,9 +122,7 @@ function stopAudioRecording() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Cancel the currently started audio recording */
|
/** Cancel the currently started audio recording */
|
||||||
function cancelAudioRecording() {
|
export function cancelAudioRecording() {
|
||||||
console.log('Canceling audio...')
|
|
||||||
|
|
||||||
//cancel the recording using the audio recording API
|
//cancel the recording using the audio recording API
|
||||||
audioRecorder.cancel()
|
audioRecorder.cancel()
|
||||||
|
|
||||||
@@ -193,50 +130,9 @@ function cancelAudioRecording() {
|
|||||||
handleHidingRecordingControlButtons()
|
handleHidingRecordingControlButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Plays recorded audio using the audio element in the HTML document
|
|
||||||
* @param {Blob} recorderAudioAsBlob - recorded audio as a Blob Object
|
|
||||||
*/
|
|
||||||
function playAudio(recorderAudioAsBlob) {
|
|
||||||
//read content of files (Blobs) asynchronously
|
|
||||||
let reader = new FileReader()
|
|
||||||
|
|
||||||
//once content has been read
|
|
||||||
reader.onload = (e) => {
|
|
||||||
//store the base64 URL that represents the URL of the recording audio
|
|
||||||
let base64URL = e.target.result
|
|
||||||
|
|
||||||
//If this is the first audio playing, create a source element
|
|
||||||
//as pre-populating the HTML with a source of empty src causes error
|
|
||||||
if (!audioElementSource)
|
|
||||||
//if it is not defined create it (happens first time only)
|
|
||||||
createSourceForAudioElement()
|
|
||||||
|
|
||||||
//set the audio element's source using the base64 URL
|
|
||||||
audioElementSource.src = base64URL
|
|
||||||
|
|
||||||
//set the type of the audio element based on the recorded audio's Blob type
|
|
||||||
let BlobType = recorderAudioAsBlob.type.includes(';')
|
|
||||||
? recorderAudioAsBlob.type.substr(0, recorderAudioAsBlob.type.indexOf(';'))
|
|
||||||
: recorderAudioAsBlob.type
|
|
||||||
audioElementSource.type = BlobType
|
|
||||||
|
|
||||||
//call the load method as it is used to update the audio element after changing the source or other settings
|
|
||||||
audioElement.load()
|
|
||||||
|
|
||||||
//play the audio after successfully setting new src and type that corresponds to the recorded audio
|
|
||||||
console.log('Playing audio...')
|
|
||||||
audioElement.play()
|
|
||||||
|
|
||||||
//Display text indicator of having the audio play in the background
|
|
||||||
displayTextIndicatorOfAudioPlaying()
|
|
||||||
}
|
|
||||||
|
|
||||||
//read content and convert it to a URL (base64)
|
|
||||||
reader.readAsDataURL(recorderAudioAsBlob)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Computes the elapsed recording time since the moment the function is called in the format h:m:s*/
|
/** Computes the elapsed recording time since the moment the function is called in the format h:m:s*/
|
||||||
function handleElapsedRecordingTime() {
|
function handleElapsedRecordingTime() {
|
||||||
|
elapsedTimeTag = document.getElementById('elapsed-time')
|
||||||
//display initial time when recording begins
|
//display initial time when recording begins
|
||||||
displayElapsedTimeDuringAudioRecording('00:00')
|
displayElapsedTimeDuringAudioRecording('00:00')
|
||||||
|
|
||||||
@@ -255,7 +151,6 @@ function handleElapsedRecordingTime() {
|
|||||||
function displayElapsedTimeDuringAudioRecording(elapsedTime) {
|
function displayElapsedTimeDuringAudioRecording(elapsedTime) {
|
||||||
//1. display the passed elapsed time as the elapsed time in the elapsedTime HTML element
|
//1. display the passed elapsed time as the elapsed time in the elapsedTime HTML element
|
||||||
elapsedTimeTag.innerHTML = elapsedTime
|
elapsedTimeTag.innerHTML = elapsedTime
|
||||||
|
|
||||||
//2. Stop the recording when the max number of hours is reached
|
//2. Stop the recording when the max number of hours is reached
|
||||||
if (elapsedTimeReachedMaximumNumberOfHours(elapsedTime)) {
|
if (elapsedTimeReachedMaximumNumberOfHours(elapsedTime)) {
|
||||||
stopAudioRecording()
|
stopAudioRecording()
|
||||||
@@ -275,9 +170,7 @@ function elapsedTimeReachedMaximumNumberOfHours(elapsedTime) {
|
|||||||
maximumRecordingTimeInHours < 10 ? '0' + maximumRecordingTimeInHours : maximumRecordingTimeInHours.toString()
|
maximumRecordingTimeInHours < 10 ? '0' + maximumRecordingTimeInHours : maximumRecordingTimeInHours.toString()
|
||||||
|
|
||||||
//if the elapsed time reach hours and also reach the maximum recording time in hours return true
|
//if the elapsed time reach hours and also reach the maximum recording time in hours return true
|
||||||
if (elapsedTimeSplit.length === 3 && elapsedTimeSplit[0] === maximumRecordingTimeInHoursAsString) return true
|
return elapsedTimeSplit.length === 3 && elapsedTimeSplit[0] === maximumRecordingTimeInHoursAsString
|
||||||
//otherwise, return false
|
|
||||||
else return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Computes the elapsedTime since the moment the function is called in the format mm:ss or hh:mm:ss
|
/** Computes the elapsedTime since the moment the function is called in the format mm:ss or hh:mm:ss
|
||||||
@@ -331,7 +224,7 @@ function computeElapsedTime(startTime) {
|
|||||||
|
|
||||||
//API to handle audio recording
|
//API to handle audio recording
|
||||||
|
|
||||||
const audioRecorder = {
|
export const audioRecorder = {
|
||||||
/** Stores the recorded audio as Blob objects of audio data as the recording continues*/
|
/** Stores the recorded audio as Blob objects of audio data as the recording continues*/
|
||||||
audioBlobs: [] /*of type Blob[]*/,
|
audioBlobs: [] /*of type Blob[]*/,
|
||||||
/** Stores the reference of the MediaRecorder instance that handles the MediaStream when recording starts*/
|
/** Stores the reference of the MediaRecorder instance that handles the MediaStream when recording starts*/
|
||||||
@@ -360,8 +253,8 @@ const audioRecorder = {
|
|||||||
audioRecorder.streamBeingCaptured = stream
|
audioRecorder.streamBeingCaptured = stream
|
||||||
|
|
||||||
//create a media recorder instance by passing that stream into the MediaRecorder constructor
|
//create a media recorder instance by passing that stream into the MediaRecorder constructor
|
||||||
audioRecorder.mediaRecorder = new MediaRecorder(stream) /*the MediaRecorder interface of the MediaStream Recording
|
audioRecorder.mediaRecorder = new MediaRecorder(stream)
|
||||||
API provides functionality to easily record media*/
|
/*the MediaRecorder interface of the MediaStream Recording API provides functionality to easily record media*/
|
||||||
|
|
||||||
//clear previously saved audio Blobs, if any
|
//clear previously saved audio Blobs, if any
|
||||||
audioRecorder.audioBlobs = []
|
audioRecorder.audioBlobs = []
|
||||||
|
|||||||
Reference in New Issue
Block a user