Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2918: Add tts on web #2985

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9530674
TTS for web part1 testing SpeechSynthesis
bahaaTuffaha Oct 7, 2024
9eb0b45
2918: More improvements to tts
bahaaTuffaha Oct 8, 2024
53a59cc
2918: more enhancement to speechSynthesis
bahaaTuffaha Oct 9, 2024
a0bc6a1
2918: Refactoring TtsPlayer
bahaaTuffaha Oct 20, 2024
d838533
2918: used easy-speech cross platform support
bahaaTuffaha Oct 25, 2024
a3a4dbf
more improvements to ttsWeb
bahaaTuffaha Oct 28, 2024
c0741da
2918: Adding TtsContextProvider and more improvements
bahaaTuffaha Oct 29, 2024
8592cec
2918 :Adding Mocks and test also adjusting the TtsHelpModal
bahaaTuffaha Oct 30, 2024
8acb476
2918: Fixing TtsPlayer test file and more improvements
bahaaTuffaha Oct 31, 2024
5ec63a2
2918: Fixing volume and changes for TtsHelpModal
bahaaTuffaha Nov 5, 2024
e4fecf3
2918: Trying to solve onEnd issue
bahaaTuffaha Nov 6, 2024
ac7f3d8
Merge branch 'main' into 2918-Add-TTS-on-web
bahaaTuffaha Nov 6, 2024
5d8a435
Merge remote-tracking branch 'origin' into 2918-Add-TTS-on-web
bahaaTuffaha Nov 11, 2024
a36dee7
2918: Fixing a bug where I cant redirect to the categories and remove…
bahaaTuffaha Nov 11, 2024
af707de
2918: Small Refactoring for codeSense
bahaaTuffaha Nov 11, 2024
8805341
2918: Adding tts to FeatureFlagsType
bahaaTuffaha Nov 12, 2024
2ee6fdb
Fixing the codeSense error for CategoriesPage at navigateToCategories
bahaaTuffaha Nov 12, 2024
b720631
2918: Splitting Tts for web into TtsPlayer and TtsContainer plus remo…
bahaaTuffaha Nov 19, 2024
366397f
Merge remote-tracking branch 'origin' into 2918-Add-TTS-on-web
bahaaTuffaha Nov 19, 2024
b24583a
2918: changes similar to tts-Android replacing react Dispatch and tit…
bahaaTuffaha Nov 20, 2024
1ff36c5
2918: Modifying to be similar to TtsOnAndroid
bahaaTuffaha Dec 11, 2024
2631024
Merge branch 'main' into 2918-Add-TTS-on-web
bahaaTuffaha Dec 11, 2024
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
Prev Previous commit
Next Next commit
2918: Fixing volume and changes for TtsHelpModal
  • Loading branch information
bahaaTuffaha committed Nov 5, 2024
commit 5ec63a2cb9918c2ae374f13ee937847113d6080b
43 changes: 1 addition & 42 deletions assets/icons/book.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 9 additions & 3 deletions translations/translations.json
Original file line number Diff line number Diff line change
@@ -5506,7 +5506,9 @@
"accessibility": "Barrierefreiheit",
"readAloud": "Vorlesefunktion",
"prev": "Zurück",
"next": "Weiter"
"next": "Weiter",
"languageNotSupported": "Sprache nicht unterstützt",
"voiceUnavailableMessage": "Diese Stimme ist im Moment nicht verfügbar; für die ausgewählte Sprache ist eine Installation erforderlich."
},
"am": {
"imprintAndContact": "ዕትም እና የግንኙነት መረጃ",
@@ -5557,7 +5559,9 @@
"sideBarCloseAriaLabel": "غلق الشريط الجانبي",
"readAloud": "قراءة بصوت عالٍ",
"prev": "السابق",
"next": "التالي"
"next": "التالي",
"languageNotSupported": "اللغة غير مدعومة",
"voiceUnavailableMessage": "هذا الصوت غير متاح في الوقت الحالي؛ يتطلب تثبيت اللغة المختارة."
},
"bg": {
"imprintAndContact": "Импресум и контакт",
@@ -5681,7 +5685,9 @@
"accessibility": "Accessibility",
"readAloud": "Read aloud",
"prev": "Prev",
"next": "Next"
"next": "Next",
"languageNotSupported": "Language not supported",
"voiceUnavailableMessage": "This voice is currently unavailable; installation is required for the selected language."
},
"es": {
"imprintAndContact": "Aviso legal y contacto",
12 changes: 6 additions & 6 deletions web/src/components/TtsHelpModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { ReactElement } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
import styled, { useTheme } from 'styled-components'

@@ -20,7 +21,7 @@ const ModalContent = styled.div`
`

const StyledWarningText = styled.div`
font-weight: 400;
font-family: ${props => props.theme.fonts.native.contentFontRegular};
font-size: 14px;
width: 70%;
margin: 10px 0;
@@ -69,24 +70,23 @@ const helpItemsData = [
]
const HelpModalItem = ({ icon, title, path }: { icon: string; title: string; path: string }) => (
<div>
<StyledItem to={path}>
<StyledItem to={path} target='_blank'>
<Icon src={icon} />
<StyledText>{title}</StyledText>
</StyledItem>
</div>
)
const TtsHelpModal = ({ closeModal }: { closeModal: () => void }): ReactElement => {
const theme = useTheme()
const { t } = useTranslation('layout')
return (
<Modal
style={{ borderRadius: 5, backgroundColor: theme.colors.ttsPlayerWarningBackground }}
title='Sprache nicht unterstützt'
title={t('languageNotSupported')}
icon={<StyledWarningIcon src={WarningIcon} />}
closeModal={closeModal}>
<ModalContent>
<StyledWarningText>
Diese Stimme ist im Moment nicht verfügbar; für die ausgewählte Sprache ist eine Installation erforderlich.
</StyledWarningText>
<StyledWarningText>{t('voiceUnavailableMessage')}</StyledWarningText>
<StyledList>
{helpItemsData.map((item, index) => (
<HelpModalItem key={`guide${index + 1}`} title={item.title} icon={item.icon} path={item.path} />
54 changes: 34 additions & 20 deletions web/src/components/TtsPlayer.tsx
Original file line number Diff line number Diff line change
@@ -148,32 +148,23 @@ const TtsPlayer = ({ languageCode }: TtsPlayerProps): ReactElement | null => {
const [sentences, setSentences] = useState<string[]>([])
const [showHelpModal, setShowHelpModal] = useState(false)
const numOfSentencesToSkip = 1
const EasySpeechInfo = EasySpeech.detect()
const EasySpeechSpeechSynthesis = EasySpeech.detect().speechSynthesis
const allowIncrement = useRef(true)
const maxTitle = 20
const isTitleLong = title.length > maxTitle ? title.substring(0, maxTitle).concat('...') : title || t('readAloud')
const userAgent = navigator.userAgent
const isAndroid = Boolean(/android/i.test(userAgent))
const volumeRef = useRef(volume)

const handleBoundary = (event: SpeechSynthesisEvent) => {
if (event.name === 'sentence' && allowIncrement.current) {
setCurrentSentenceIndex((prevIndex: number) => prevIndex + 1)
}
allowIncrement.current = true
if (currentSentencesIndex >= sentences.length - 1) {
setCurrentSentenceIndex(0)
setIsPlaying(false)
setExpandPlayer(false)
}
}

useEffect(() => {
EasySpeech.init({ maxTimeout: 5000, interval: 250 }).catch(e => reportError(e))
return () => {
EasySpeech.cancel()
EasySpeech.reset()
setCurrentSentenceIndex(0)
}
}, [])

useEffect(() => {
@@ -197,41 +188,62 @@ const TtsPlayer = ({ languageCode }: TtsPlayerProps): ReactElement | null => {
appendPeriod(paragraphs)

const textContent = tempDiv.textContent || tempDiv.innerText
setSentences(segment(languageCode, textContent))
setSentences([title.concat('.'), ...segment(languageCode, textContent)])
setCurrentSentenceIndex(0)
}, [content, languageCode])

return () => {
if (EasySpeechSpeechSynthesis) {
EasySpeech.cancel()
}
setCurrentSentenceIndex(0)
setIsPlaying(false)
setExpandPlayer(false)
}
}, [EasySpeechSpeechSynthesis, content, languageCode, title])

const startReading = async (index = currentSentencesIndex) => {
EasySpeech.cancel()
const voices = EasySpeech.voices()
const selectedVoice = voices.find((voice: SpeechSynthesisVoice) => voice.lang.startsWith(languageCode))
const currentVolume = volumeRef.current
if (selectedVoice == null) {
setSentences([])
setShowHelpModal(true)
return
}

await EasySpeech.speak({
text: sentences.slice(index).join(' '),
voice: selectedVoice,
pitch: 1,
rate: 1,
volume,
volume: currentVolume,
boundary: e => handleBoundary(e),
end: () => {
// Added if statement to make sure that firefox is running this only at the end
if (currentSentencesIndex + 2 >= sentences.length - 1) {
setCurrentSentenceIndex(0)
setIsPlaying(false)
setExpandPlayer(false)
}
},
}).catch(() => null)
}

const pauseReading = () => {
EasySpeech.pause()
setIsPlaying(false)
}
const withSkip = async (action: () => Promise<void>) => {

const boundaryGuard = async (action: () => Promise<void>) => {
allowIncrement.current = true
try {
await action()
} finally {
allowIncrement.current = false
}
}

const togglePlayPause = () => {
if (isPlaying) {
pauseReading()
@@ -240,7 +252,7 @@ const TtsPlayer = ({ languageCode }: TtsPlayerProps): ReactElement | null => {
EasySpeech.resume()
} else {
setIsPlaying(true)
withSkip(() => startReading())
boundaryGuard(() => startReading())
}
setExpandPlayer(!isPlaying)
}
@@ -251,7 +263,7 @@ const TtsPlayer = ({ languageCode }: TtsPlayerProps): ReactElement | null => {
}
setCurrentSentenceIndex(prevIndex => {
const newIndex = Math.min(prevIndex + numOfSentencesToSkip, sentences.length - 1)
withSkip(() => startReading(newIndex))
boundaryGuard(() => startReading(newIndex))
return newIndex
})
}
@@ -262,16 +274,16 @@ const TtsPlayer = ({ languageCode }: TtsPlayerProps): ReactElement | null => {
}
setCurrentSentenceIndex(prevIndex => {
const newIndex = Math.max(0, prevIndex - numOfSentencesToSkip)
withSkip(() => startReading(newIndex))
boundaryGuard(() => startReading(newIndex))
return newIndex
})
}

const handleVolumeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newVolume = parseFloat(event.target.value)
setVolume(newVolume)
EasySpeech.cancel()
withSkip(() => startReading())
volumeRef.current = newVolume
boundaryGuard(() => startReading(currentSentencesIndex))
}

const handleClose = () => {
@@ -281,6 +293,7 @@ const TtsPlayer = ({ languageCode }: TtsPlayerProps): ReactElement | null => {
setCurrentSentenceIndex(0)
setIsPlaying(false)
}

if (visible) {
return (
<>
@@ -329,6 +342,7 @@ const TtsPlayer = ({ languageCode }: TtsPlayerProps): ReactElement | null => {
</>
)
}

return null
}

4 changes: 3 additions & 1 deletion web/src/components/__mocks__/easy-speech.tsx
Original file line number Diff line number Diff line change
@@ -5,7 +5,9 @@ const MockTts = {
cancel: jest.fn(),
pause: jest.fn(),
resume: jest.fn(),
detect: jest.fn(),
detect: jest.fn().mockReturnValue({
speechSynthesis: {},
}),
voices: jest.fn().mockReturnValue([{ lang: 'en-US' }]),
}

4 changes: 2 additions & 2 deletions web/src/components/__tests__/TtsPlayer.spec.tsx
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ describe('TtsPlayer', () => {

expect(EasySpeech.speak).toHaveBeenCalledWith(
expect.objectContaining({
text: 'This is a test.',
text: 'test. This is a test.',
voice: { lang: 'en-US' },
pitch: 1,
rate: 1,
@@ -113,6 +113,6 @@ describe('TtsPlayer', () => {

unmount()

expect(EasySpeech.reset).toHaveBeenCalled()
expect(EasySpeech.cancel).toHaveBeenCalled()
})
})