@ihabdevteam/core 0.322.0 โ 0.323.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +517 -0
- package/dist/components.cjs.js +183 -183
- package/dist/components.esm.css +1 -1
- package/dist/hooks.cjs.js +3 -3
- package/dist/i18n.cjs.js +3 -3
- package/dist/ihabdevteam-core.cjs.js +187 -187
- package/dist/ihabdevteam-core.d.ts +54 -16
- package/dist/packages/core/src/components/Accordion.js +3 -3
- package/dist/packages/core/src/components/Alert.js +2 -2
- package/dist/packages/core/src/components/BarChart.js +120 -119
- package/dist/packages/core/src/components/BottomNav.js +18 -18
- package/dist/packages/core/src/components/BulkBar.js +25 -25
- package/dist/packages/core/src/components/Button.js +16 -16
- package/dist/packages/core/src/components/Calendar.js +17 -17
- package/dist/packages/core/src/components/ChartSection.js +30 -30
- package/dist/packages/core/src/components/CountTile.js +38 -38
- package/dist/packages/core/src/components/DatePicker.js +59 -59
- package/dist/packages/core/src/components/Dialog.js +14 -14
- package/dist/packages/core/src/components/Effect/EffectPartyPopperHost.js +8 -8
- package/dist/packages/core/src/components/FilterPanel.js +6 -6
- package/dist/packages/core/src/components/FilterSectionInfo.js +6 -6
- package/dist/packages/core/src/components/GameIntro.js +1 -1
- package/dist/packages/core/src/components/Icon.js +2 -2
- package/dist/packages/core/src/components/ItemActions.js +16 -16
- package/dist/packages/core/src/components/LandingHeader.js +6 -6
- package/dist/packages/core/src/components/LineChart.js +24 -24
- package/dist/packages/core/src/components/Loader.js +5 -5
- package/dist/packages/core/src/components/NoticeBox.js +3 -3
- package/dist/packages/core/src/components/PageTitle.js +2 -2
- package/dist/packages/core/src/components/Pagination.js +4 -4
- package/dist/packages/core/src/components/Panel.js +2 -2
- package/dist/packages/core/src/components/PanelRow.js +6 -13
- package/dist/packages/core/src/components/PassCard/PassCard.js +9 -9
- package/dist/packages/core/src/components/PassCard/passCardProduct.js +2 -2
- package/dist/packages/core/src/components/PaymentPlan.js +5 -5
- package/dist/packages/core/src/components/Popup/ConfirmPopup.js +29 -29
- package/dist/packages/core/src/components/Popup/CouponInputPopup.js +2 -2
- package/dist/packages/core/src/components/Popup/DeviceSideTable.js +18 -25
- package/dist/packages/core/src/components/Popup/ListPopup.js +3 -3
- package/dist/packages/core/src/components/Popup/LoadTablePopup.js +7 -7
- package/dist/packages/core/src/components/Popup/Popup.js +27 -27
- package/dist/packages/core/src/components/Popup/SaveTablePopup.js +5 -5
- package/dist/packages/core/src/components/Popup/TrainingResultPopup.js +4 -4
- package/dist/packages/core/src/components/Popup/customerDetailUtils.js +18 -18
- package/dist/packages/core/src/components/Popup/useDiscardGuard.js +4 -4
- package/dist/packages/core/src/components/ProgressIndicator.js +5 -5
- package/dist/packages/core/src/components/PromoBannerBg/promoBannerBg.js +16 -16
- package/dist/packages/core/src/components/Rating.js +20 -20
- package/dist/packages/core/src/components/Report/ReportDocument.js +21 -21
- package/dist/packages/core/src/components/RingChart.js +5 -5
- package/dist/packages/core/src/components/SectionHeadFloat.js +15 -15
- package/dist/packages/core/src/components/SegmentedControl.js +34 -34
- package/dist/packages/core/src/components/SideNav.js +37 -37
- package/dist/packages/core/src/components/Slider.js +2 -2
- package/dist/packages/core/src/components/SoundCompass.js +5 -5
- package/dist/packages/core/src/components/Table.js +257 -98
- package/dist/packages/core/src/components/Tabs.js +12 -12
- package/dist/packages/core/src/components/TextField.js +34 -34
- package/dist/packages/core/src/components/Toast.js +6 -6
- package/dist/packages/core/src/components/Tooltip/MenuTooltip.js +4 -4
- package/dist/packages/core/src/components/Tooltip/Tooltip.js +22 -22
- package/dist/packages/core/src/components/Tooltip/TrainingTooltip.js +4 -4
- package/dist/packages/core/src/components/TopBanner.js +28 -28
- package/dist/packages/core/src/components/Training/AudioPlayerWaveForm.js +11 -11
- package/dist/packages/core/src/components/Training/CategoryIcon.js +2 -2
- package/dist/packages/core/src/components/Training/NoisePlayerContext.js +12 -12
- package/dist/packages/core/src/components/Training/NoiseSettingsTooltip.js +2 -2
- package/dist/packages/core/src/components/Training/TrainingBlock.js +11 -11
- package/dist/packages/core/src/components/Training/TrainingBlockButton.js +10 -10
- package/dist/packages/core/src/components/Training/TrainingBlockGrid.js +21 -21
- package/dist/packages/core/src/components/Training/TrainingCard.css +13 -1
- package/dist/packages/core/src/components/Training/TrainingCard.js +2 -2
- package/dist/packages/core/src/components/Training/TrainingCardGroup.js +6 -6
- package/dist/packages/core/src/components/Training/TrainingPagePopup.js +81 -81
- package/dist/packages/core/src/components/Training/TrainingPagePopupBottom.js +17 -17
- package/dist/packages/core/src/components/Training/TrainingStepAudioPlayer.js +127 -127
- package/dist/packages/core/src/components/Training/TrainingStepCard.js +18 -18
- package/dist/packages/core/src/components/Training/TrainingStepConfirmWithText.js +4 -4
- package/dist/packages/core/src/components/Training/TrainingStepOrderList.js +12 -12
- package/dist/packages/core/src/components/Training/TrainingStepTextBox.js +12 -12
- package/dist/packages/core/src/components/Training/TrainingStepTitle.js +4 -4
- package/dist/packages/core/src/components/Training/TrainingWordCard.js +17 -17
- package/dist/packages/core/src/components/Training/WordTablePopup.js +5 -5
- package/dist/packages/core/src/components/Training/audioPlaybackRules.js +8 -8
- package/dist/packages/core/src/components/Training/audioTimeline.js +25 -25
- package/dist/packages/core/src/components/Training/oneShotSound.js +13 -13
- package/dist/packages/core/src/components/Training/trainingMissStats.js +20 -20
- package/dist/packages/core/src/components/Training/trainingPopupUtils.js +7 -7
- package/dist/packages/core/src/components/Training/useAudioPreview.js +15 -15
- package/dist/packages/core/src/components/Training/useFloatingFocus.js +14 -14
- package/dist/packages/core/src/components/Training/useNoiseFade.js +22 -22
- package/dist/packages/core/src/components/Training/useObjectUrls.js +2 -2
- package/dist/packages/core/src/components/Training/usePlaybackSequencer.d.ts +2 -2
- package/dist/packages/core/src/components/Training/usePlaybackSequencer.js +23 -23
- package/dist/packages/core/src/components/Training/useProgressTicker.js +33 -33
- package/dist/packages/core/src/components/Training/useTTSPlayback.js +17 -17
- package/dist/packages/core/src/components/Training/useWaveReady.js +11 -11
- package/dist/packages/core/src/components/UpdateAlert.js +6 -6
- package/dist/packages/core/src/components/Word/PhonemeSelectBlock.js +7 -1
- package/dist/packages/core/src/components/Word/TableFilterPanel.js +3 -3
- package/dist/packages/core/src/components/Word/WordSearchResultBlock.js +10 -10
- package/dist/packages/core/src/components/Word/WordTableBlock.js +28 -28
- package/dist/packages/core/src/components/Word/WordTableButton.css +16 -2
- package/dist/packages/core/src/components/Word/WordTableThumbnail.css +6 -1
- package/dist/packages/core/src/components/Word/generatedDraft.js +14 -14
- package/dist/packages/core/src/components/Word/useWordTableDisplaySettings.js +7 -7
- package/dist/packages/core/src/components/Word/wordScoreStats.js +15 -15
- package/dist/packages/core/src/components/Word/wordTablePrint.js +10 -10
- package/dist/packages/core/src/components/Word/wordTableRecommend.js +11 -11
- package/dist/packages/core/src/components/Word/wordTableReview.js +28 -28
- package/dist/packages/core/src/components/Word/wordTableSettings.d.ts +8 -11
- package/dist/packages/core/src/components/Word/wordTableSettings.js +48 -36
- package/dist/packages/core/src/components/WordTable.js +130 -130
- package/dist/packages/core/src/components/barChartData.d.ts +16 -1
- package/dist/packages/core/src/components/barChartData.js +49 -29
- package/dist/packages/core/src/components/barChartTypes.js +8 -8
- package/dist/packages/core/src/components/tableTrackList.js +11 -11
- package/dist/packages/core/src/components/updateAlertStack.js +28 -28
- package/dist/packages/core/src/components/useBarChartControl.js +4 -6
- package/dist/packages/core/src/components/useComboboxSuggestions.js +17 -17
- package/dist/packages/core/src/components/useToastLifecycle.js +18 -18
- package/dist/packages/core/src/components/wordTableLayout.d.ts +17 -4
- package/dist/packages/core/src/components/wordTableLayout.js +5 -5
- package/dist/packages/core/src/context/I18nProvider.js +6 -6
- package/dist/packages/core/src/hooks/focusReturn.js +27 -27
- package/dist/packages/core/src/hooks/inputModality.js +13 -13
- package/dist/packages/core/src/hooks/touchToggle.js +39 -39
- package/dist/packages/core/src/hooks/useAnchoredMenu.js +30 -30
- package/dist/packages/core/src/hooks/useDailyTrainingSelection.js +28 -28
- package/dist/packages/core/src/hooks/useDocumentTitle.js +10 -10
- package/dist/packages/core/src/hooks/useEditableList.js +4 -4
- package/dist/packages/core/src/hooks/useFocusTrap.js +2 -2
- package/dist/packages/core/src/hooks/useFontScale.js +6 -6
- package/dist/packages/core/src/hooks/useIcon.js +7 -7
- package/dist/packages/core/src/hooks/useListControls.js +2 -2
- package/dist/packages/core/src/hooks/usePageResetOnChange.js +3 -3
- package/dist/packages/core/src/hooks/usePasswordChange.js +4 -4
- package/dist/packages/core/src/hooks/usePendingPress.js +7 -7
- package/dist/packages/core/src/hooks/useRelativeDay.js +4 -4
- package/dist/packages/core/src/hooks/useSoundCompassExam.js +6 -6
- package/dist/packages/core/src/hooks/useTablePaging.d.ts +0 -11
- package/dist/packages/core/src/hooks/useTablePaging.js +29 -34
- package/dist/packages/core/src/hooks/useTrainingSessionLog.js +35 -35
- package/dist/packages/core/src/hooks/useVisitSchedule.js +4 -4
- package/dist/packages/core/src/hooks/useWordAttributeFilter.js +14 -14
- package/dist/packages/core/src/hooks/useWordMeanings.js +5 -5
- package/dist/packages/core/src/hooks/useWordTrainingFilter.js +7 -7
- package/dist/packages/core/src/i18n/locales/en.json +13 -2
- package/dist/packages/core/src/i18n/locales/ko.json +12 -1
- package/dist/packages/core/src/types/wordSearch.d.ts +9 -0
- package/dist/packages/core/src/utils/audioMeter.js +5 -5
- package/dist/packages/core/src/utils/audioPeaks.d.ts +1 -1
- package/dist/packages/core/src/utils/audioPeaks.js +18 -18
- package/dist/packages/core/src/utils/blockGrid.js +4 -4
- package/dist/packages/core/src/utils/chunkSkew.js +9 -9
- package/dist/packages/core/src/utils/clipboard.js +3 -3
- package/dist/packages/core/src/utils/dailySelectionUtils.js +12 -12
- package/dist/packages/core/src/utils/date.js +12 -12
- package/dist/packages/core/src/utils/devWarn.js +4 -4
- package/dist/packages/core/src/utils/deviceKind.d.ts +37 -7
- package/dist/packages/core/src/utils/deviceKind.js +53 -10
- package/dist/packages/core/src/utils/errorMessage.js +7 -7
- package/dist/packages/core/src/utils/filterPanelSummary.js +14 -14
- package/dist/packages/core/src/utils/format.js +4 -4
- package/dist/packages/core/src/utils/getTrainingData.js +9 -9
- package/dist/packages/core/src/utils/imageConfig.js +17 -17
- package/dist/packages/core/src/utils/koreanSyllable.js +18 -18
- package/dist/packages/core/src/utils/navScroll.js +2 -2
- package/dist/packages/core/src/utils/phonemeConvert.d.ts +7 -0
- package/dist/packages/core/src/utils/phonemeConvert.js +19 -10
- package/dist/packages/core/src/utils/phonemeGroups.js +2 -2
- package/dist/packages/core/src/utils/phonemePairSearch.d.ts +1 -0
- package/dist/packages/core/src/utils/phonemePairSearch.js +118 -102
- package/dist/packages/core/src/utils/phonemeSectionCodes.d.ts +52 -0
- package/dist/packages/core/src/utils/phonemeSectionCodes.js +76 -0
- package/dist/packages/core/src/utils/prefetchChunk.js +6 -6
- package/dist/packages/core/src/utils/safeHref.js +4 -4
- package/dist/packages/core/src/utils/savedWordTables.js +5 -5
- package/dist/packages/core/src/utils/speak.js +12 -12
- package/dist/packages/core/src/utils/textSort.d.ts +0 -10
- package/dist/packages/core/src/utils/textSort.js +38 -48
- package/dist/packages/core/src/utils/theme.js +3 -3
- package/dist/packages/core/src/utils/trainingItemResults.js +3 -3
- package/dist/packages/core/src/utils/ttsConfig.js +2 -2
- package/dist/packages/core/src/utils/wordAttributeFilterCodes.js +45 -45
- package/dist/packages/core/src/utils/wordContrast.js +3 -3
- package/dist/packages/core/src/utils/wordPairIndex.js +4 -4
- package/dist/packages/core/src/utils/wordTrainingGenerators.js +4 -4
- package/dist/packages/core/src/utils/wordTrainingPool.js +15 -15
- package/dist/packages/core/src/utils/wordTrainingRuntime.js +6 -6
- package/dist/packages/core/src/utils/wornDate.d.ts +3 -3
- package/dist/packages/core/src/utils/wornDate.js +14 -14
- package/dist/packages/core/src/version.d.ts +1 -1
- package/dist/packages/core/src/version.js +1 -1
- package/dist/utils.cjs.js +4 -4
- package/package.json +1 -1
|
@@ -83,17 +83,17 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
83
83
|
const [stateLocal, setStateLocal] = useState('active');
|
|
84
84
|
/* ์ด์ ์ ๊ฐ์ง ํฐ '๋ฃ๊ธฐ' ๋จ์ถ๋ฅผ ๋๋ฅด๋ฉด ๊ทธ ๋จ์ถ๊ฐ ์ฌ๋ผ์ง๋ค โ ์ฌ์ ๋ชจ๋๊ฐ ๋๋ฉด ์ด์ ์ '์ฌ์ / ๋ฉ์ถค' ์ผ๋ก ์ฎ๊ธด๋ค
|
|
85
85
|
(์ฎ๊ธฐ์ง ์์ผ๋ฉด ์ด์ ์ด body ๋ก ๋จ์ด์ ธ ํค๋ณด๋ ์ฌ์ฉ์๊ฐ ๋ฉ์ถ ๋จ์ถ๋ฅผ ๋ค์ ์ฐพ์์ผ ํ๋ค). */
|
|
86
|
-
const
|
|
86
|
+
const moveFocusToListenRef = useRef(false);
|
|
87
87
|
useEffect(() => {
|
|
88
|
-
if (stateLocal !== 'playMode' ||
|
|
88
|
+
if (stateLocal !== 'playMode' || !moveFocusToListenRef.current)
|
|
89
89
|
return;
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
90
|
+
moveFocusToListenRef.current = false;
|
|
91
|
+
const active = typeof document !== 'undefined' ? document.activeElement : null;
|
|
92
|
+
if (active && active !== document.body)
|
|
93
93
|
return;
|
|
94
|
-
const
|
|
94
|
+
const playPauseBtn = btnsRef.current?.querySelectorAll('button')[1];
|
|
95
95
|
try {
|
|
96
|
-
|
|
96
|
+
playPauseBtn?.focus();
|
|
97
97
|
}
|
|
98
98
|
catch (e) { }
|
|
99
99
|
}, [stateLocal]);
|
|
@@ -107,7 +107,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
107
107
|
* ๊ทธ๋์ ๋ฒํธ๋ `currentIndex` ์์ ์ฝ๊ณ , ์ฌ๊ธฐ์๋ **๋ฐํ์ง ๋ง์ง๋ง** ๋ ๋ค.
|
|
108
108
|
* ๋์ ๋ฐ๋ก ๋ค๋ฉด ์ธ์ ๊ฐ ํ์ชฝ๋ง ๊ฐฑ์ ๋๋ ์๋ฆฌ๊ฐ ์๊ธด๋ค(์ค์ ๋ก `goToTrack` ์ด
|
|
109
109
|
* ๋ฒํธ๋ง ๋ฐ๊พธ๊ณ ์ด ๊ฐ์ ํจ๊ณผ์ ๊ธฐ๋๊ณ ์์๋ค). */
|
|
110
|
-
const [
|
|
110
|
+
const [showRange, setShowRange] = useState(false);
|
|
111
111
|
const [totalDuration, setTotalDuration] = useState(0);
|
|
112
112
|
// ๋ฌธ์ฅ(๋ฉ์ธ) ์ฌ์ ์๋ยท๋ณผ๋ฅจ โ ํ๋จ ์์์ค์ ํดํ๊ณผ ๊ณต์ (NoisePlayerContext)
|
|
113
113
|
const playbackRate = noisePlayer?.playbackRate ?? 1.0;
|
|
@@ -138,16 +138,16 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
138
138
|
/* **์๊ฐํ์ ์๋ฆฌยท์๋ยท๋ธ ํ์ด๋จธยท๋ฉ์ถ ์๋ฆฌ๋ ์งํ๊ธฐ๊ฐ ์ฅ๋ค**((85) 2a~2c, 2026-10-06).
|
|
139
139
|
๋ฌป๋ ์ชฝ(์์ ยท ํฑ ยท ํ ๊ธ ยท ์๋ฌผ์ )์ **์๋ฆฌ๋ฅผ ๋ฐ์ ์ ์์ผ๋ก ์
ํ์ง ์๊ณ ** ์งํ๊ธฐ์๊ฒ
|
|
140
140
|
๋ฌป๋๋ค โ `isRunning()` ํ๋๋ค. ๊ทธ ์
์ด ์ธ ๋ฒ๋ก ํฉ์ด์ ธ ์๋ ๊ฒ์ด ์ค๋ ๊ฒฐํจ ์ฌ์ฏ์ ๋ฟ๋ฆฌ์๋ค. */
|
|
141
|
-
const
|
|
141
|
+
const sequencer = usePlaybackSequencer({ mountedRef });
|
|
142
142
|
/* `onEnded` ๊ฐ ์ ์ด ๋๋ "์ฌ๊ธฐ์๋ถํฐ ์ด์ด ๊ฐ๋ค" โ ํ ๊ธ์ด ๋ฉ์ถ ๋ ์งํ๊ธฐ์ ๋๊ธด๋ค */
|
|
143
|
-
const
|
|
143
|
+
const continueRef = useRef(null);
|
|
144
144
|
/* ์ด๋ฒคํธ ์์ก์ด๋ ํ ๋ฒ๋ง ๋ถ์ผ๋ฏ๋ก prop ์ ref ๋ก ๋ค๊ณ ๋ณธ๋ค โ ์ ๊ทธ๋ฌ๋ฉด ๋ก์ ๊ฐ์ ์ด๋ค
|
|
145
145
|
(์ด ํ์ผ์ด `disabledRef`ยท`noiseSettingsRef` ์ ์ฐ๋ ๊ฒ๊ณผ ๊ฐ์ ๊ฒฐ). */
|
|
146
146
|
const leadInMsRef = useRef(leadInMs);
|
|
147
147
|
const tailMsRef = useRef(tailMs);
|
|
148
148
|
useEffect(() => { leadInMsRef.current = Math.max(0, leadInMs || 0); }, [leadInMs]);
|
|
149
149
|
useEffect(() => { tailMsRef.current = Math.max(0, tailMs || 0); }, [tailMs]);
|
|
150
|
-
const { start: startTicker, stop: stopTicker, settleTo: settleTicker, } = useProgressTicker({ audioRef, fillRef, waveProgressRef, sourcesRef, currentIndexRef, sequencer:
|
|
150
|
+
const { start: startTicker, stop: stopTicker, settleTo: settleTicker, } = useProgressTicker({ audioRef, fillRef, waveProgressRef, sourcesRef, currentIndexRef, sequencer: sequencer, leadInMs, tailMs });
|
|
151
151
|
/* ํํ์ด ์๋ฆฌ๋ฅผ ์ก์ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๊ท์น์ useWaveReady ๊ฐ ๋ค๊ณ ์๋ค โ
|
|
152
152
|
ref ๋ยทํจ์ ๋์ "์ค๋น ์ ๋จ์ผ๋ก ๋๋๋ฆฌ๊ธฐ"๊ฐ ์ผ๊ณฑ ๊ตฐ๋ฐ ํฉ์ด์ ธ ์๋ ๊ฒ์ ๋ชจ์๋ค. */
|
|
153
153
|
const { onReadyChange: handleWaveReadyChange, waitForReady: waitForWaveReady, markNotReady: markWaveNotReady, } = useWaveReady({ showSoundWaveForm, showNoiseWaveForm });
|
|
@@ -174,9 +174,9 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
174
174
|
noisePlayer,
|
|
175
175
|
audioRef,
|
|
176
176
|
playbackStateRef,
|
|
177
|
-
sequencer:
|
|
177
|
+
sequencer: sequencer,
|
|
178
178
|
});
|
|
179
|
-
const { startTTS:
|
|
179
|
+
const { startTTS: beginTTS, stopTTS } = useTTSPlayback({
|
|
180
180
|
ttsText,
|
|
181
181
|
playbackRate,
|
|
182
182
|
// TTS ์์ฑ = ๋ฌธ์ ์๋ฆฌ = ์ ์ฒด ์๋ฆฌ
|
|
@@ -201,11 +201,11 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
201
201
|
setNoticeMessage(t('training.ttsUnavailable', '์ด ๊ธฐ๊ธฐ์์๋ ์์ฑ์ ๋ค๋ ค์ค ์ ์์ต๋๋ค.'));
|
|
202
202
|
}
|
|
203
203
|
catch (_) { }
|
|
204
|
-
|
|
204
|
+
beginTTS(text);
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
acquireSound('tts');
|
|
208
|
+
beginTTS(text);
|
|
209
209
|
};
|
|
210
210
|
const requestedSources = Array.isArray(soundSrc)
|
|
211
211
|
? soundSrc.filter(Boolean)
|
|
@@ -228,17 +228,17 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
228
228
|
*
|
|
229
229
|
* ์ด์ ์ธ๋ ํ๋๋ค. `๋ฉ์ถค์๋()` ๋ ์ธ๋๋ฅผ ์ฌ๋ฆฌ๊ณ ์๋ฆฌ๋ฅผ ๋น์ด๋ค.
|
|
230
230
|
*/
|
|
231
|
-
const
|
|
231
|
+
const abortIntent = () => { sequencer.abort(); };
|
|
232
232
|
/* ์คํผ์ปค๋ ํ๋(currentSound.ts) โ ํ ํ๋ฉด์ ๋ค๋ฅธ ์ฌ์๊ธฐ(๋ฌธ์ฅ ๋ฃ๊ธฐยท๋ฌธ์ ๋ฃ๊ธฐ)ยท๋ฑ๋ง ์นด๋ยท๋ฏธ๋ฆฌ ๋ฃ๊ธฐ๊ฐ ์ธ๋ฆฌ๋ฉด ์ด ์ฌ์๊ธฐ๋
|
|
233
233
|
๋ฉ์ถ๋ค. ์ ์๋ ๋ฑ๋ง ์นด๋๋ง ์๋ฌผ์ ๋ฅผ ์ก์, ๋ฃ๊ธฐ ๋จ๊ณ๊ฐ ๋์ธ ํ๋ฉดยท๋ฃ๊ธฐ+์นด๋ ๋จ๊ณ์์ ๋ ์๋ฆฌ๊ฐ ํจ๊ป ์ธ๋ ธ๋ค.
|
|
234
234
|
๋ฉ์ถค ๋จ์ถ์ ๊ฐ๊ฒ ๋ฉ์ถ๋ค(์ด์ด ๋ฃ๊ธฐ ๊ฐ๋ฅ). ํจ์๋ ํ ๋ฒ์ด์ด์ผ ์๋ฌผ์ ๊ฐ '๋ด ๊ฒ' ์ ์์๋ณธ๋ค. */
|
|
235
235
|
const stopTTSRef = useRef(null);
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
if (
|
|
240
|
-
|
|
241
|
-
|
|
236
|
+
const soundKindRef = useRef(null);
|
|
237
|
+
const releaseSoundRef = useRef(null);
|
|
238
|
+
const stopSoundRef = useRef(null);
|
|
239
|
+
if (!stopSoundRef.current) {
|
|
240
|
+
stopSoundRef.current = () => {
|
|
241
|
+
releaseSoundRef.current = null;
|
|
242
242
|
const a = audioRef.current;
|
|
243
243
|
/* **๋ธ ์ค์ด๋ฉด ์์๋ ๋ฉ์ถฐ ์๋ค** โ `!a.paused` ๋ง ๋ณด๋ฉด ์ด ๋ฌธ์ ์ ์ง๋, ๋จ์ด ์คํผ์ปค๋ฅผ
|
|
244
244
|
๊ฐ์ ธ๊ฐ๋ **๋ธ์ ์ ๊ฒ์ธ ์ค ์๊ณ ๊นจ์ด๋ ์๋ฆฌ๋ฅผ ๋๋ค**(๊ฐ์ง ์๊ณ ์ค์ธก, ๋ ๊ธธ ๋ค:
|
|
@@ -247,8 +247,8 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
247
247
|
์ธ๋ฆฐ๋ค" ๊ณ ๋ณด๊ณ ํ ๊ทธ ์๋ฆฌ์ **๋ธ ํ**์ด๋ค.
|
|
248
248
|
์ค๋ ๊ฐ์ ๊ผด์ ์ฌ์ฏ ๋ฒ์งธ ๋ง๋๋ค(์์ ๊ฐ์ ยท ์ฌ์ํ ยท ํ ๊ธ ยท ๊ทธ ํ ๊ธ์ ๋ ๋ฒ์งธ ๋ฒ ยท
|
|
249
249
|
๋ฒํธ ๋จ์ถ ยท ์ฌ๊ธฐ). ์ด์ `isRunning()` ํ๋๋ก ๋ฌป๋๋ค. */
|
|
250
|
-
if (
|
|
251
|
-
|
|
250
|
+
if (soundKindRef.current === 'audio' && a && (!a.paused || sequencer.isRunning())) {
|
|
251
|
+
abortIntent();
|
|
252
252
|
try {
|
|
253
253
|
a.pause();
|
|
254
254
|
}
|
|
@@ -259,7 +259,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
259
259
|
fadeOutNoise();
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
|
-
else if (
|
|
262
|
+
else if (soundKindRef.current === 'tts') {
|
|
263
263
|
try {
|
|
264
264
|
stopTTSRef.current?.();
|
|
265
265
|
}
|
|
@@ -267,14 +267,14 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
const acquireSound = (kind) => {
|
|
271
|
+
soundKindRef.current = kind;
|
|
272
|
+
releaseSoundRef.current = claimSound(stopSoundRef.current);
|
|
273
273
|
};
|
|
274
274
|
// ๋ฉ์ถคยท์ ์งยท์๋ฃ ๋ค์๋ ์๋ฆฌ๋ฅผ ๋น์ด๋ค โ ๋จ์ด ์ธ๋ฆด ๋ ๋๋ ์ฌ์๊ธฐ์ ์ํ๋ฅผ ๊ฑด๋๋ฆฌ์ง ์๊ฒ
|
|
275
|
-
const
|
|
276
|
-
useEffect(() => () => { if (
|
|
277
|
-
stopSoundIfOwner(
|
|
275
|
+
const releaseSound = () => { releaseSoundRef.current?.(); releaseSoundRef.current = null; };
|
|
276
|
+
useEffect(() => () => { if (stopSoundRef.current)
|
|
277
|
+
stopSoundIfOwner(stopSoundRef.current); }, []);
|
|
278
278
|
/* ์ด๋ฒคํธ ์์ก์ดยท๋น๋๊ธฐ ํ๋ฆ ์์์ **์ง๊ธ ๊ฐ**์ ์ฝ์ด์ผ ํ๋ค โ ํจ๊ณผ๊ฐ ๋ค์ ๋ ๋๊น์ง
|
|
279
279
|
๊ธฐ๋ค๋ฆฌ๋ฉด ์ด๋ฏธ ์ค์ ์๋ฆฌ์ ์ ๋น ๋ฅด๊ธฐ๊ฐ ๊ฑธ๋ฆฐ๋ค(์ด ํ์ผ์ `disabledRef`ยท`leadInMsRef` ์ ๊ฐ์ ๊ฒฐ). */
|
|
280
280
|
const playbackRateRef = useRef(playbackRate);
|
|
@@ -285,7 +285,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
285
285
|
* `load()` ๋ ๋ช
์ธ๋๋ก `playbackRate` ๋ฅผ **`defaultPlaybackRate` ๋ก ๋๋๋ฆฐ๋ค.** ๊ธฐ๋ณธ๊ฐ์
|
|
286
286
|
* ์ ์ธ์ ๋๋ฉด ๊ทธ ๋๋๋ฆผ์ด ๋ 1 ์ด๋ผ, ์๋ฆฌ๋ฅผ ์ค์ ๋๋ง๋ค ์ฌ์ฉ์๊ฐ ๊ณ ๋ฅธ ๋น ๋ฅด๊ธฐ๊ฐ ์ง์์ง๋ค.
|
|
287
287
|
*/
|
|
288
|
-
const
|
|
288
|
+
const applyPlaybackRate = useCallback((a) => {
|
|
289
289
|
const r = playbackRateRef.current || 1.0;
|
|
290
290
|
try {
|
|
291
291
|
a.defaultPlaybackRate = r;
|
|
@@ -309,12 +309,12 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
309
309
|
* ์๋ฆฌ๋ฅผ ํ๋๋ก ๋ชจ์๋ค โ ํฉ์ด์ง ๋ค์ฏ์ ๊ธ๋ก ๋จ์ํ์ง ์๊ณ **์
์ผ๋ก ๋ง๋๋ค**
|
|
310
310
|
* (`test/audioPlaybackRateSurvivesLoad.test.tsx` ๊ฐ `load()` ์๋ฆฌ ์๋ฅผ ์ผ๋ค).
|
|
311
311
|
*/
|
|
312
|
-
const
|
|
312
|
+
const loadSound = useCallback((a, src) => {
|
|
313
313
|
a.src = src;
|
|
314
|
-
|
|
314
|
+
applyPlaybackRate(a); // defaultPlaybackRate ๋ฅผ ๋จผ์ ์ธ์ ๋๋ค โ load() ๊ฐ ๊ทธ๊ฒ์ผ๋ก ๋๋๋ฆฐ๋ค
|
|
315
315
|
a.load();
|
|
316
|
-
|
|
317
|
-
}, [
|
|
316
|
+
applyPlaybackRate(a); // ๋๋๋ฆฐ ๋ค ํ ๋ฒ ๋ โ ๊ธฐ๋ณธ๊ฐ์ ๋ชป ์ธ์ฐ๋ ํ๊ฒฝ์์๋ ์ ๋ค
|
|
317
|
+
}, [applyPlaybackRate]);
|
|
318
318
|
/**
|
|
319
319
|
* **์๋ฃ ์ฐ์ถ์ ๊ฑท๋๋ค** โ ์ ์ฌ์์ด ์๋ฉด ์ง๋ ์๋ฃ์ ์์ทจ๊ฐ ๊ทธ ์์ ๋จ์์ ์ ๋๋ค.
|
|
320
320
|
*
|
|
@@ -332,17 +332,17 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
332
332
|
* ๋ถ๋ฅธ๋ค(๋ค์๋ฃ๊ธฐ ยท โถ ยท ๋ฒํธ ๋จ์ถ ยท ๋ถ๋ฌ์ค๊ธฐ๊ฐ ๋ค ๊ทธ ๊ธธ์ด๋ค). ๊ฑท๋ ๊ณณ์ด ์ฌ๋ฟ์ด๋ฉด
|
|
333
333
|
* **ํ ๊ณณ์ด ์ ๋ฐ๋ผ๊ฐ๋ค** โ ์ค๋ ๋ฐค ๋ด๋ด ๊ทธ ๊ฒฐ์ด์๋ค.
|
|
334
334
|
*/
|
|
335
|
-
const
|
|
335
|
+
const clearDoneEffect = useCallback(() => {
|
|
336
336
|
if (fillOutResetRef.current) {
|
|
337
337
|
clearTimeout(fillOutResetRef.current);
|
|
338
338
|
fillOutResetRef.current = null;
|
|
339
339
|
}
|
|
340
340
|
setFillCompletionPhase('idle');
|
|
341
341
|
}, []);
|
|
342
|
-
const tryPlayWithRetry = useCallback(async (a, attempts = 3, delayMs = 50,
|
|
342
|
+
const tryPlayWithRetry = useCallback(async (a, attempts = 3, delayMs = 50, leadElapsed = 0) => {
|
|
343
343
|
/* ์ง๋ ์๋ฃ์ ์ ๋ ์ฐ์ถ์ ๋จผ์ ๊ฑท๋๋ค โ ์ ์ค๋ช
์ฐธ๊ณ */
|
|
344
|
-
|
|
345
|
-
const
|
|
344
|
+
clearDoneEffect();
|
|
345
|
+
const myGeneration = sequencer.generation();
|
|
346
346
|
/* **์ด์ด ํธ๋ ๊ฒ์๋ ์๋ธ์ ๋ค์ ์ ์ค๋ค**(์ฌ์ฉ์ ์ ๊ณ , 2026-10-06).
|
|
347
347
|
์๋ธ์ *์๋ฆฌ ์*์ ๊ตฌ๊ฐ์ด๋ค. ์๋ฆฌ๊ฐ **์ด๋ฏธ ์์๋** ๊ฒ์ ์ด์ด ํธ๋๋ฐ ์๋ธ์ ๋ ์ฃผ๋ฉด
|
|
348
348
|
์๋ฆฌ๋ ๋ฉ์ถ ์๋ฆฌ(`currentTime`)์์ ์ด์ด ๊ฐ๋๋ฐ **์งํ์ 0 ๋ถํฐ ๋ค์ ์ผ๋ค** โ ๋ง๊ท
|
|
@@ -356,16 +356,16 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
356
356
|
> ์๋ฆฌ ์ค์ ๋ฉ์ท๋ค ์ด์ ๋๋ ๊ท๋ฅผ ๋ค์ ์ฑ๋น์ํค๋ ค **์๋ธ์ ์ฃผ๋** ์ชฝ์ด ๋์ ์๋
|
|
357
357
|
> ์๋ค. ๊ทธ๋ฌ๋ฉด ์๊ฐํ๋ฅผ *์๋ธ + ๋จ์ ์๋ฆฌ + ๋ค๋ธ* ์ผ๋ก ๋ค์ ๊ทธ๋ ค์ผ ์ด๊ธ๋์ง ์๋๋ค.
|
|
358
358
|
> ๊ทธ๊ฒ์ **์ฌ์ฅ๋๊ป ์ฌ์ญ ์ผ**์ด๋ผ ์ง๊ธ์ "์๋ธ์ ์๋ฆฌ ์" ์ด๋ผ๋ ๋ป ๊ทธ๋๋ก ๋๋ค. */
|
|
359
|
-
const
|
|
359
|
+
const resuming = !!a && a.currentTime > 0 && !a.ended;
|
|
360
360
|
/* ์๋ธ์์ ๋ฉ์ท๋ค ์ด์ผ๋ฉด **๋จ์ ๋งํผ๋ง** ์ฑ์ด๋ค โ ๋ธ๋ ์๊ฐํ์ ์ผ๋ถ๋ผ ์ฒ์๋ถํฐ ๋ค์
|
|
361
361
|
์ธ๋ฉด ๊ทธ ์๋ฆฌ์์ ์ด์ด์ง์ง ์๋๋ค(์ฌ์ฉ์ ๊ฒฐ์ , 2026-10-06). */
|
|
362
|
-
const
|
|
363
|
-
if (
|
|
362
|
+
const leadRemaining = Math.max(0, leadInMsRef.current - Math.max(0, leadElapsed));
|
|
363
|
+
if (leadRemaining > 0 && !resuming) {
|
|
364
364
|
/* **์คํผ์ปค๋ ๋ธ์ด ์์๋ ๋ ์ก๋๋ค**((85) 2b, 2026-10-06). ์ ์๋ `await a.play()` ๋ฐ๋ก
|
|
365
365
|
์์์ ์ก์๋๋ฐ, ๊ทธ๋ฌ๋ฉด **์๋ธ ๋ด๋ด ์ฃผ์ธ์ด ์๋ค** โ ๊ทธ ์ฌ์ด ๋จ์ด ์๋ฆฌ๋ฅผ ๋ด๋ ์ด์ชฝ์
|
|
366
366
|
๋ชป ๋ฉ์ถ๊ณ , ๋ธ์ด ๋๋๋ฉด ๋์ด ํจ๊ป ์ธ๋ฆฐ๋ค. ์์์ ์ด๋ฏธ ๋ธ๋ถํฐ ๊น๋ฆฌ๋(0.268.0)
|
|
367
367
|
**์๋ฆฌ๋ฅผ ๋ด๊ณ ์๋ ๊ฒ์ด ๋ง๋ค.** */
|
|
368
|
-
|
|
368
|
+
acquireSound('audio');
|
|
369
369
|
startTicker();
|
|
370
370
|
/* ์๋ธ๋ถํฐ **์ฌ์ ์ค**์ด๋ค โ ์ฌ์ํ์ด ์ด์ ์๋ธ๋ถํฐ ๋ณด์ด๋ฏ๋ก((86)) ๋จ์ถ๋ โธ ๋ก ์์ผ
|
|
371
371
|
๋๋ฅด๋ ๋ป๊ณผ ๋ง๋๋ค. โถ ๋ก ๋๋ฉด "๋ฉ์ถ๋ ค๊ณ โถ ๋ฅผ ๋๋ฅด๋" ๊ผด์ด ๋๋ค. */
|
|
@@ -383,24 +383,24 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
383
383
|
}
|
|
384
384
|
catch (e) { }
|
|
385
385
|
}
|
|
386
|
-
if (!(await
|
|
386
|
+
if (!(await sequencer.waitPad(leadRemaining, 'lead', leadElapsed)))
|
|
387
387
|
return false;
|
|
388
|
-
if (!mountedRef.current ||
|
|
388
|
+
if (!mountedRef.current || !sequencer.isCurrent(myGeneration))
|
|
389
389
|
return false;
|
|
390
390
|
}
|
|
391
|
-
|
|
391
|
+
sequencer.enterSound();
|
|
392
392
|
for (let i = 0; i < attempts; i++) {
|
|
393
|
-
if (!mountedRef.current ||
|
|
393
|
+
if (!mountedRef.current || !sequencer.isCurrent(myGeneration))
|
|
394
394
|
return false;
|
|
395
395
|
try {
|
|
396
|
-
|
|
396
|
+
acquireSound('audio');
|
|
397
397
|
await a.play();
|
|
398
398
|
return true;
|
|
399
399
|
}
|
|
400
400
|
catch (err) {
|
|
401
401
|
const errName = err instanceof Error ? err.name : (typeof err === 'object' && err && 'name' in err ? err.name : undefined);
|
|
402
402
|
if (errName === 'AbortError') {
|
|
403
|
-
if (
|
|
403
|
+
if (!sequencer.isCurrent(myGeneration))
|
|
404
404
|
return false;
|
|
405
405
|
await new Promise((res) => setTimeout(res, delayMs));
|
|
406
406
|
continue;
|
|
@@ -424,7 +424,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
424
424
|
}
|
|
425
425
|
catch (_) { }
|
|
426
426
|
return false;
|
|
427
|
-
}, [t, startTicker,
|
|
427
|
+
}, [t, startTicker, sequencer, fadeInNoise, noiseSettingsRef, clearDoneEffect]);
|
|
428
428
|
/**
|
|
429
429
|
* **๋ค๋ธ(์ฌ์ด ๋ธ)์์ ๋ฉ์ท๋ค ์ด์ ๋** โ ๋จ์ ๋งํผ ๋๊ณ ํ๋ ์ผ์ ์๋๋ค.
|
|
430
430
|
*
|
|
@@ -432,9 +432,9 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
432
432
|
* ์งง์์ง๊ณ ์ฌ์๋ฐ๊ฐ ๊ทธ ์๊ฐ ํ๋ค โ ์ฌ์ฉ์๊ฐ ์ง์ ๊ฒ์ด ๊ทธ ์๋ฆฌ๋ค. `onEnded` ์ ์ฌ์ฌ๊ณผ
|
|
433
433
|
* **๊ฐ์ ๋ชจ์**์ด์ด์ผ ํ๋ฏ๋ก(์งํ๋ฐ ์ผ๊ธฐ ยท ์์ ยท ์ธ๋ ๊ฐ๋ฆผ ยท `leave()`) ์ฌ๊ธฐ๋ ๊ทธ๋๋ก ๋๋ค.
|
|
434
434
|
*/
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const
|
|
435
|
+
const continueAfterTail = useCallback((elapsed) => {
|
|
436
|
+
const myGeneration = sequencer.generation();
|
|
437
|
+
const remaining = Math.max(0, tailMsRef.current - Math.max(0, elapsed));
|
|
438
438
|
startTicker();
|
|
439
439
|
const ns = noiseSettingsRef.current;
|
|
440
440
|
if (ns?.enabled && (ns.volume ?? 0) > 0) {
|
|
@@ -443,21 +443,21 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
443
443
|
}
|
|
444
444
|
catch (e) { }
|
|
445
445
|
}
|
|
446
|
-
const
|
|
447
|
-
|
|
446
|
+
const resume = () => { try {
|
|
447
|
+
continueRef.current?.();
|
|
448
448
|
}
|
|
449
449
|
catch (e) { } };
|
|
450
|
-
if (
|
|
451
|
-
|
|
452
|
-
|
|
450
|
+
if (remaining <= 0) {
|
|
451
|
+
sequencer.leave();
|
|
452
|
+
resume();
|
|
453
453
|
return;
|
|
454
454
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (
|
|
458
|
-
|
|
455
|
+
sequencer.waitPad(remaining, 'tail', elapsed).then((mayContinue) => {
|
|
456
|
+
sequencer.leave();
|
|
457
|
+
if (mayContinue && sequencer.isCurrent(myGeneration))
|
|
458
|
+
resume();
|
|
459
459
|
}).catch(() => { });
|
|
460
|
-
}, [
|
|
460
|
+
}, [sequencer, startTicker, fadeInNoise, noiseSettingsRef]);
|
|
461
461
|
const attachAudioListeners = useCallback(() => {
|
|
462
462
|
const a = audioRef.current;
|
|
463
463
|
if (!a)
|
|
@@ -465,7 +465,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
465
465
|
const onPlay = () => {
|
|
466
466
|
setPlaybackState("playing");
|
|
467
467
|
setStateLocal("playMode");
|
|
468
|
-
|
|
468
|
+
setShowRange(true);
|
|
469
469
|
startTicker();
|
|
470
470
|
// ๋ฌธ์ฅ์ Web Audio ๊ทธ๋ํ(gainยท๋ฏธํฐ)์ ํ์ด๋ค โ ์ฌ์ ์ ์ค์ฒ ์์ ์ resume, ์ปจํ
์คํธ๊ฐ ๋ฌ๋ฆฌ๊ณ ๋์์ผ
|
|
471
471
|
// ํ์ฐ๋ฏ๋ก ๊ทธ ์ ์ ์์๊ฐ ์ ๋ณผ๋ฅจ์ผ๋ก ์ง์ ์ธ๋ฆฐ๋ค(๋ฌด์ ๋ฐฉ์ง). ํ์ด ๋ค ๋ณผ๋ฅจ์ GainNode ๋ก ๊ฑธ๋ฆฐ๋ค.
|
|
@@ -501,17 +501,17 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
501
501
|
๋ค์ ์๋ฆฌ๊ฐ ์์ผ๋ฉด ๊ทธ ์๋ฆฌ์ **์ ๋ธ**์ด ์ด์ด ๋ถ์ด ์ฌ์ด๊ฐ ๋์ ๋ํ ๋งํผ์ด ๋๋ค โ
|
|
502
502
|
๋๋ฅผ ๋์ ๋ธ๊ณผ ์ฌ์ด์ ๋ธ์ ๊ฐ๋ฅด๋ ์๋ฆฌ๊ฐ ์ฌ๊ธฐ๋ค. */
|
|
503
503
|
const onEnded = () => {
|
|
504
|
-
const
|
|
504
|
+
const continuePlay = () => {
|
|
505
505
|
try {
|
|
506
506
|
const sref = sourcesRef.current;
|
|
507
507
|
const next = nextTrackIndex(sref, currentIndexRef.current);
|
|
508
508
|
if (next !== null) {
|
|
509
509
|
currentIndexRef.current = next;
|
|
510
510
|
setCurrentIndex(next);
|
|
511
|
-
|
|
511
|
+
setShowRange(true);
|
|
512
512
|
const nextSrc = sref?.[next] && sref[next].src;
|
|
513
513
|
if (nextSrc && a) {
|
|
514
|
-
|
|
514
|
+
loadSound(a, nextSrc);
|
|
515
515
|
tryPlayWithRetry(a).catch(() => { });
|
|
516
516
|
return;
|
|
517
517
|
}
|
|
@@ -529,7 +529,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
529
529
|
}
|
|
530
530
|
catch (e) { }
|
|
531
531
|
stopTicker();
|
|
532
|
-
|
|
532
|
+
setShowRange(false);
|
|
533
533
|
// ๋ง์ง๋ง ํธ๋ ์ข
๋ฃ ์ ์์ ์ ์ง
|
|
534
534
|
fadeOutNoise();
|
|
535
535
|
// Route through handleEnd (via ref) so stateLocal transitions to 'active' before onDone fires.
|
|
@@ -541,21 +541,21 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
541
541
|
catch (e) { }
|
|
542
542
|
};
|
|
543
543
|
/* ๋ฉ์ท๋ค ๋ค์ ๋๋ฅผ ๋ **์ฌ๊ธฐ์๋ถํฐ** ์๋๋ค โ ์งํ๊ธฐ๊ฐ ๊ทธ ์๋ฆฌ๋ฅผ ์๋ค */
|
|
544
|
-
|
|
544
|
+
continueRef.current = continuePlay;
|
|
545
545
|
if (tailMsRef.current > 0) {
|
|
546
546
|
/* ๊ธฐ๋ค๋ฆฌ๋ ๋์ ์งํ๋ฐ๋ฅผ ๋๋ฆฐ๋ค โ `pause` ๊ฐ ์ด๋ฏธ ์ธ์ ์ ์ ์์ด ๋ค์ ์ผ ๋ค.
|
|
547
547
|
๊ทธ ์ฌ์ด ๋ฉ์ถ๊ฑฐ๋ ๋ ๋๋ฉด `waitPad` ๊ฐ ๊ฑฐ์ง์ ์ฃผ๊ณ ์๋ฌด ์ผ๋ ์ ํ๋ค. */
|
|
548
548
|
startTicker();
|
|
549
|
-
|
|
549
|
+
sequencer.waitPad(tailMsRef.current, 'tail').then((mayContinue) => {
|
|
550
550
|
/* ๋ค๋ธ์ด ๋๋ฌ๋ค โ ์๋ฆฌ๋ ๋น์ฐ๋ **์ธ๋๋ ์ ์ฌ๋ฆฐ๋ค**(`abort` ๊ฐ ์๋๋ค). ์ฌ๋ฆฌ๋ฉด
|
|
551
551
|
๋ฐ๋ก ์๋์์ ์ด์ด ๊ฐ ์ฌ์ฌ๊น์ง ์ ๊ฐ ๋ฒ๋ฆฐ ๊ฒ์ด ๋๋ค. */
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
|
|
552
|
+
sequencer.leave();
|
|
553
|
+
if (mayContinue)
|
|
554
|
+
continuePlay();
|
|
555
555
|
}).catch(() => { });
|
|
556
556
|
return;
|
|
557
557
|
}
|
|
558
|
-
|
|
558
|
+
continuePlay();
|
|
559
559
|
};
|
|
560
560
|
a.addEventListener("play", onPlay);
|
|
561
561
|
a.addEventListener("pause", onPause);
|
|
@@ -569,7 +569,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
569
569
|
}
|
|
570
570
|
catch (e) { }
|
|
571
571
|
};
|
|
572
|
-
}, [fadeInNoise, fadeOutNoise, tryPlayWithRetry, noiseSettingsRef, startTicker, stopTicker,
|
|
572
|
+
}, [fadeInNoise, fadeOutNoise, tryPlayWithRetry, noiseSettingsRef, startTicker, stopTicker, sequencer]);
|
|
573
573
|
useEffect(() => {
|
|
574
574
|
const cleanup = attachAudioListeners();
|
|
575
575
|
return cleanup;
|
|
@@ -735,21 +735,21 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
735
735
|
*
|
|
736
736
|
* ์ฐจ๋ก๋ ์ด๋ ๋ค: ๋๋ฆผํ โ ์ค๋น ๋ โ **์ฌ์ํยทํํยท์ฌ์๋ฐ๊ฐ ๋ณด์** โ ์๋ธ(์์) โ ์๋ฆฌ.
|
|
737
737
|
*/
|
|
738
|
-
const
|
|
738
|
+
const openPlayPanel = () => {
|
|
739
739
|
setStateLocal('playMode');
|
|
740
740
|
setCurrentIndex(0);
|
|
741
|
-
|
|
741
|
+
setShowRange(true);
|
|
742
742
|
};
|
|
743
743
|
const loadAndPlay = async (a, src) => {
|
|
744
744
|
markCompletedByEnd(false);
|
|
745
745
|
fetchingRef.current = false;
|
|
746
|
-
|
|
746
|
+
loadSound(a, src);
|
|
747
747
|
await waitForCanPlay(a);
|
|
748
748
|
// ๊ธฐ๋ค๋ฆฌ๋ ์ฌ์ด ๋ ๋ฌ์ผ๋ฉด(false) ์ด์ด ๊ฐ์ง ์๋๋ค โ ๋ผ์ด์ง <audio> ๋ฅผ ํ๋ ๊ฒ
|
|
749
749
|
if ((await waitForWaveReady()) === false)
|
|
750
750
|
return;
|
|
751
751
|
setIsFetchingUrl(false);
|
|
752
|
-
|
|
752
|
+
openPlayPanel(); // ์๋ธ์ด ์์๋๊ธฐ **์ ์** โ ๋ธ ๋์ ํ์ด ๋น์ง ์๊ฒ
|
|
753
753
|
await tryPlayWithRetry(a);
|
|
754
754
|
};
|
|
755
755
|
const fetchAndPrepareFromTTSUrl = async (ttsUrl, playAfterLoad = false) => {
|
|
@@ -896,7 +896,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
896
896
|
fetchingRef.current = false;
|
|
897
897
|
if (first.src && a) {
|
|
898
898
|
try {
|
|
899
|
-
|
|
899
|
+
loadSound(a, first.src);
|
|
900
900
|
// canplay ์ด๋ฒคํธ ๋๊ธฐ โ load() ์งํ play() ํธ์ถ ์ ๋ฐ์ํ๋ AbortError ๋ฐฉ์ง.
|
|
901
901
|
// ์งง์ ํ์ผ๋ canplay๊ฐ ๋น ๋ฅด๊ฒ ๋ฐ์ํ๋ฏ๋ก ์ฌ์ ์ง์ฐ ์์.
|
|
902
902
|
// (๊ฐ์ ๊ธฐ๋ค๋ฆผ์ด ์ด ํ์ผ ์์ชฝ์ waitForCanPlay ๋ก ์ด๋ฏธ ์์๋ค. ์ฌ๊ธฐ์ ํ ๋ฒ
|
|
@@ -909,7 +909,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
909
909
|
// ๋ค๋ฆ๊ฒ ๋ํ๋๋ ๋น ํ๋ฉด ๊ตฌ๊ฐ๋ ์๊ธฐ์ง ์๋๋ก ํ๋ค.
|
|
910
910
|
if (showSpinner)
|
|
911
911
|
setIsFetchingUrl(false);
|
|
912
|
-
|
|
912
|
+
openPlayPanel(); // ์๋ธ์ด ์์๋๊ธฐ **์ ์** โ ๋ธ ๋์ ํ์ด ๋น์ง ์๊ฒ
|
|
913
913
|
if (a)
|
|
914
914
|
await tryPlayWithRetry(a);
|
|
915
915
|
}
|
|
@@ -924,7 +924,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
924
924
|
}
|
|
925
925
|
setStateLocal('playMode');
|
|
926
926
|
setCurrentIndex(0);
|
|
927
|
-
|
|
927
|
+
setShowRange(true);
|
|
928
928
|
}
|
|
929
929
|
return finalSources.length > 0;
|
|
930
930
|
}
|
|
@@ -944,7 +944,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
944
944
|
* 300ms ์ โธ โ ์์ `play()` ๊ฐ ๋ ๋ถ๋ฆฌ๊ณ **๊ฐ์ ๋จ์ด๊ฐ ๋ ๋ฒ ํ์ด์ก๋ค**(2622ms ยท 2945ms).
|
|
945
945
|
* ๋ด ๋ชป์ ์ ์ฅ์ ์๋ฆฌ๋ก๋ง ๋๋ ค์ ํต๊ณผํ๋ค โ **๊ณ ์น ๋ฒ๋ง ์ฌ๊ณ ์์๋ค.**
|
|
946
946
|
*/
|
|
947
|
-
const
|
|
947
|
+
const toggleElement = async (a) => {
|
|
948
948
|
/* **๋ธ ์ค์ด๋ฉด ์ฌ์ ์ค์ด๋ค**(๋ง๊ท ์์ฒญ (85) 0๋จ๊ณ์์ ์ฐพ์ ๊ฒฐํจ, 2026-10-06).
|
|
949
949
|
์ ์๋ `a.paused` ๋ง ๋ดค๋ค. ๊ทธ๋ฐ๋ฐ ๋ธ ๋์ ๋ฉ์ธ ์์๋ **๋ฉ์ถฐ ์๋ค** โ ์๋ธ์๋ ์์ง
|
|
950
950
|
`play()` ์ ์ด๊ณ ์ฌ์ดยท๋ค๋ธ์๋ ์ด๋ฏธ `ended` ๋ค. ๊ทธ๋์ โธ ๋ก ๋ณด์ด๋ ๋จ์ถ๋ฅผ ๋ธ ์ค์
|
|
@@ -953,8 +953,8 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
953
953
|
+1000ms ์ ํ ๋ฒ, ์๋ ์ฌ์ฌ์ด ๋ฟ๋ +1500ms ์ ๋ ํ ๋ฒ).
|
|
954
954
|
๊ฐ์ ํ์ด๋จธ๊ฐ 0.268.0 ์์ ๋ฐฐ์ด ๊ฒ๊ณผ ๊ฐ์ ๊ฒ์ ์ด ๋จ์ถ๊ฐ ๋ชป ๋ฐฐ์ฐ๊ณ ์์๋ค โ
|
|
955
955
|
๋ธ์ ์๋ฆฌ๊ฐ ์ ๋๋ ๊ตฌ๊ฐ์ด์ง **๋ฉ์ถ ๊ตฌ๊ฐ์ด ์๋๋ค.** */
|
|
956
|
-
const
|
|
957
|
-
if (a.paused &&
|
|
956
|
+
const padRunning = sequencer.isRunning();
|
|
957
|
+
if (a.paused && !padRunning) {
|
|
958
958
|
if (completedByEndRef.current) {
|
|
959
959
|
await handleReplay();
|
|
960
960
|
return;
|
|
@@ -964,26 +964,26 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
964
964
|
๋ค์ ์ธ์ง ์๋๋ค. */
|
|
965
965
|
/* **๋ฉ์ถ ์๋ฆฌ์์ ์ด์ด ๊ฐ๋ค** โ ์ด๋์ ๋ช ms ์๋์ง๋ ์งํ๊ธฐ๊ฐ ์๋ค.
|
|
966
966
|
๋ธ๋ ์๊ฐํ์ ์ผ๋ถ๋ผ **๋จ์ ๋งํผ๋ง** ์ฑ์ด๋ค(์ฌ์ฉ์ ๊ฒฐ์ , 2026-10-06). */
|
|
967
|
-
const
|
|
968
|
-
if (
|
|
967
|
+
const resumedAt = sequencer.resume();
|
|
968
|
+
if (resumedAt) {
|
|
969
969
|
setPlaybackState("playing");
|
|
970
|
-
if (
|
|
971
|
-
|
|
970
|
+
if (resumedAt.phase === 'tail') {
|
|
971
|
+
continueAfterTail(resumedAt.elapsed);
|
|
972
972
|
return;
|
|
973
973
|
}
|
|
974
|
-
await tryPlayWithRetry(a, 3, 50,
|
|
974
|
+
await tryPlayWithRetry(a, 3, 50, resumedAt.elapsed);
|
|
975
975
|
return;
|
|
976
976
|
}
|
|
977
977
|
await tryPlayWithRetry(a);
|
|
978
978
|
setPlaybackState("playing");
|
|
979
979
|
}
|
|
980
980
|
else {
|
|
981
|
-
|
|
982
|
-
|
|
981
|
+
sequencer.pause();
|
|
982
|
+
abortIntent();
|
|
983
983
|
a.pause();
|
|
984
984
|
setPlaybackState("paused");
|
|
985
985
|
/* ๋ธ ์ค์๋ `pause` ์ฌ๊ฑด์ด ์ ์จ๋ค(์์๊ฐ ์ด๋ฏธ ๋ฉ์ถฐ ์๋ค) โ ๊ทธ ์ฌ๊ฑด์ด ํ๋ ์ผ์ ์ฌ๊ธฐ์ ํ๋ค */
|
|
986
|
-
if (
|
|
986
|
+
if (padRunning) {
|
|
987
987
|
stopTicker();
|
|
988
988
|
fadeOutNoise();
|
|
989
989
|
}
|
|
@@ -1044,16 +1044,16 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1044
1044
|
if (stateLocal === 'playMode') {
|
|
1045
1045
|
/* ์ค๋์ค ํ์ผ๋ก ๋ณํ๋ ๊ฒฝ์ฐ audioRef๋ก ์ ์ด. <audio> ๋ ๋ ์์ผ๋ฏ๋ก ๊ทธ๊ฒ๋ง ๋ณด๋ฉด ์์ฑ ํฉ์ฑ์ผ๋ก ๋งํ๋ ์ค(๋ฆฌ์กธ๋ฒ ์์ยท์คํจ)์๋
|
|
1046
1046
|
์ด ๊ธธ๋ก ๋ค์ด๊ฐ ์๋ฆฌ ์๋ <audio> ๋ฅผ ์ฌ์ํ๋ ค ๋ค๊ณ ๋ง์ ๋ฉ์ถ์ง ์์๋ค โ ํฉ์ฑ๊ธฐ๊ฐ ๋งํ๋ ์ค์ด๊ณ <audio> ์ ์๋ฆฌ๊ฐ ์์ผ๋ฉด ํฉ์ฑ๊ธฐ๋ฅผ ๋ฉ์ถ๋ค. */
|
|
1047
|
-
const
|
|
1048
|
-
if (a &&
|
|
1049
|
-
await
|
|
1047
|
+
const speakingByTTS = !!synth && (synth.speaking || synth.paused) && !(a && (a.currentSrc || a.getAttribute('src')));
|
|
1048
|
+
if (a && !speakingByTTS) {
|
|
1049
|
+
await toggleElement(a); // ์ ์ฅ์ ์๋ฆฌ ๊ฐ๋์ **๊ฐ์ ํ ๋ฒ**
|
|
1050
1050
|
return;
|
|
1051
1051
|
}
|
|
1052
1052
|
// speechSynthesis fallback
|
|
1053
1053
|
try {
|
|
1054
1054
|
if (synth && synth.paused) {
|
|
1055
1055
|
try {
|
|
1056
|
-
|
|
1056
|
+
acquireSound('tts');
|
|
1057
1057
|
synth.resume();
|
|
1058
1058
|
setPlaybackState('playing');
|
|
1059
1059
|
}
|
|
@@ -1079,30 +1079,30 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1079
1079
|
try {
|
|
1080
1080
|
if (first.src && a) {
|
|
1081
1081
|
try {
|
|
1082
|
-
|
|
1083
|
-
|
|
1082
|
+
loadSound(a, first.src);
|
|
1083
|
+
openPlayPanel(); // ์๋ธ์ด ์์๋๊ธฐ **์ ์**
|
|
1084
1084
|
if (a)
|
|
1085
1085
|
await tryPlayWithRetry(a);
|
|
1086
1086
|
}
|
|
1087
1087
|
catch (e) { }
|
|
1088
1088
|
}
|
|
1089
|
-
|
|
1089
|
+
openPlayPanel();
|
|
1090
1090
|
return;
|
|
1091
1091
|
}
|
|
1092
1092
|
catch (e) { }
|
|
1093
1093
|
}
|
|
1094
1094
|
setStateLocal("load");
|
|
1095
1095
|
// ์ฑ์ด ์ค ๊ฒฝ๋ก ๊ท์น(resolvePath ๋ฑ)์ด ๋์ ธ๋ ๋ชป ์ป์ ๊ฒ๊ณผ ๊ฐ์ด ๋ค๋ฃฌ๋ค โ ๊ฑฐ์ ์ด ํ๋ฌ 'load' ์ ๊ฐํ๋ค
|
|
1096
|
-
let
|
|
1096
|
+
let got;
|
|
1097
1097
|
try {
|
|
1098
|
-
|
|
1098
|
+
got = await fetchAndPrepare(true);
|
|
1099
1099
|
}
|
|
1100
1100
|
catch (e) {
|
|
1101
|
-
|
|
1101
|
+
got = false;
|
|
1102
1102
|
}
|
|
1103
1103
|
/* ์ธ ์์ค๋ฅผ ํ๋๋ ๋ชป ์ป์ผ๋ฉด(๊ฒฝ๋ก๋ฅผ ๋ชป ์ ํ ์ฝ๋๋ฟ ๋ฑ) 'load' ์ ๊ฐํ ๋ฃ๊ธฐ ๋จ์ถ๊ฐ ์ฌ๋ผ์ง๊ณ ์๋ฌด ์กฐ์๋
|
|
1104
1104
|
์ ๋๋ค โ ์๋ฆฌ๊ณ ๋๋ฅผ ์ ์๋ ์ํ๋ก ๋๋๋ฆฐ๋ค. */
|
|
1105
|
-
if (
|
|
1105
|
+
if (got === false && mountedRef.current) {
|
|
1106
1106
|
try {
|
|
1107
1107
|
setNoticeMessage(t('training.audioFetchError', '์ฌ์ด๋ ์์ค๋ฅผ ๊ฐ์ ธ์ค์ง ๋ชปํ์ต๋๋ค.'));
|
|
1108
1108
|
}
|
|
@@ -1116,7 +1116,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1116
1116
|
if (stateLocal === "playMode" || stateLocal === "active") {
|
|
1117
1117
|
try {
|
|
1118
1118
|
if (a)
|
|
1119
|
-
await
|
|
1119
|
+
await toggleElement(a);
|
|
1120
1120
|
}
|
|
1121
1121
|
catch (e) {
|
|
1122
1122
|
const errName = e instanceof Error ? e.name : (typeof e === 'object' && e && 'name' in e ? e.name : undefined);
|
|
@@ -1153,8 +1153,8 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1153
1153
|
};
|
|
1154
1154
|
const handleStop = () => {
|
|
1155
1155
|
// ๊ธฐ๋ค๋ฆฌ๋ ํธ๋ ์ด๋ยท์ฌ์๋๊ฐ ์ ์ง ๋ค์ ์๋ฆฌ๋ฅผ ๋ด์ง ์๊ฒ
|
|
1156
|
-
|
|
1157
|
-
|
|
1156
|
+
abortIntent();
|
|
1157
|
+
releaseSound();
|
|
1158
1158
|
/* **`ttsMode` ๋ผ๊ณ `<audio>` ๋ฅผ ์ ๋ฉ์ถ๋ฉด ์ ๋๋ค**(2026-10-06). ์ ์๋ ์ฌ๊ธฐ์ `stopTTS()`
|
|
1159
1159
|
๋ง ํ๊ณ **์ผ์ฐ ๋์๊ฐ๋ค.** ๊ทธ๋ฐ๋ฐ TTS ์๋ ๊ธธ์ด ๋์ด๋ค โ ๋ฆฌ์กธ๋ฒ๊ฐ ์์ผ๋ฉด **์ฃผ์๋ฅผ ๋ฐ์
|
|
1160
1160
|
`<audio>` ๋ก ํผ๋ค**(๊ทธ ๊ธธ์ ๋ธยทํํยท๋ฒํธ ๋จ์ถ๊ฐ ๋ค ๋ฌ๋ ค ์๋ค). ๊ธฐ๊ธฐ ๋ชฉ์๋ฆฌ๋ก ๋งํ ๋๋ง
|
|
@@ -1182,7 +1182,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1182
1182
|
setPlaybackState('stopped');
|
|
1183
1183
|
setStateLocal('active');
|
|
1184
1184
|
setProgress(0);
|
|
1185
|
-
|
|
1185
|
+
setShowRange(false);
|
|
1186
1186
|
};
|
|
1187
1187
|
/**
|
|
1188
1188
|
* ํธ๋์ ๊ฐ์ ๋ผ์ฐ๊ณ ์ฌ์ํ๋ค โ ์ด์ ยท๋ค์ยท๋ฒํธ ๋จ์ถ๊ฐ ๋ชจ๋ ์ด ํ ๋ฒ์ ์ด๋ค.
|
|
@@ -1207,28 +1207,28 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1207
1207
|
`a.src` ๋ฅผ ๋ค์ ์๋ฆฌ๋ก ๋ฐ๊ฟ ๋์์๋ค.
|
|
1208
1208
|
๊ทธ ํ ํฐ์ **์ธ๋์ ๊ฐ์ ์ผ**์ด์๋ค((85) 2d) โ `๋ฉ์ถค์๋()` ๊ฐ ๋ ๋ค ์ฌ๋ฆฌ๊ณ ์ฌ๊ธฐ์
|
|
1209
1209
|
๋ ๋ค ์ง์๋ค. ์ธ๋ ํ๋๋ก ํฉ์น๋ค: `abort()` ๋ก ๊ฑท๊ณ , **๊ทธ ๋ค์** ์ ์ธ๋๋ฅผ ์ง๋๋ค. */
|
|
1210
|
-
|
|
1211
|
-
const
|
|
1210
|
+
abortIntent();
|
|
1211
|
+
const myGeneration = sequencer.generation();
|
|
1212
1212
|
const a = audioRef.current;
|
|
1213
1213
|
const sref = sourcesRef.current;
|
|
1214
1214
|
const src = sref?.[index]?.src;
|
|
1215
1215
|
try {
|
|
1216
1216
|
markCompletedByEnd(false);
|
|
1217
1217
|
setCurrentIndex(index);
|
|
1218
|
-
|
|
1218
|
+
setShowRange(true); // ์ฌ์์ด ์์๋๊ธฐ ์ ์๋ ๋ฐํ๋ค โ ์์ ์ ํจ๊ณผ๊ฐ ํด ์ฃผ๋ ์ผ
|
|
1219
1219
|
currentIndexRef.current = index;
|
|
1220
1220
|
if (!src || !a)
|
|
1221
1221
|
return;
|
|
1222
1222
|
// ๊ฐ์ ๊ตฌ๊ฐ์ ๋ค์ ํธ๋ ๊ฒ์ด๋ฉด ํํ์ ๊ทธ๋๋ก๋ค โ ๊ดํ ๊ธฐ๋ค๋ฆฌ์ง ์๋๋ค.
|
|
1223
1223
|
if (!sameSegment)
|
|
1224
1224
|
markWaveNotReady();
|
|
1225
|
-
|
|
1225
|
+
loadSound(a, src);
|
|
1226
1226
|
if (!sameSegment && (await waitForWaveReady()) === false)
|
|
1227
1227
|
return;
|
|
1228
|
-
if (
|
|
1228
|
+
if (!sequencer.isCurrent(myGeneration))
|
|
1229
1229
|
return;
|
|
1230
1230
|
await tryPlayWithRetry(a);
|
|
1231
|
-
if (
|
|
1231
|
+
if (!sequencer.isCurrent(myGeneration))
|
|
1232
1232
|
return;
|
|
1233
1233
|
setPlaybackState('playing');
|
|
1234
1234
|
setStateLocal('playMode');
|
|
@@ -1269,8 +1269,8 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1269
1269
|
const holdDelay = 200;
|
|
1270
1270
|
const outDelay = 340;
|
|
1271
1271
|
markCompletedByEnd(true);
|
|
1272
|
-
|
|
1273
|
-
|
|
1272
|
+
abortIntent();
|
|
1273
|
+
releaseSound();
|
|
1274
1274
|
try {
|
|
1275
1275
|
setProgress(1);
|
|
1276
1276
|
}
|
|
@@ -1305,7 +1305,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1305
1305
|
setPlaybackState('stopped');
|
|
1306
1306
|
}
|
|
1307
1307
|
catch (e) { }
|
|
1308
|
-
|
|
1308
|
+
setShowRange(false);
|
|
1309
1309
|
if (disabledRef.current)
|
|
1310
1310
|
return;
|
|
1311
1311
|
try {
|
|
@@ -1360,10 +1360,10 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1360
1360
|
try {
|
|
1361
1361
|
const a = audioRef.current;
|
|
1362
1362
|
if (a && typeof a.playbackRate === 'number')
|
|
1363
|
-
|
|
1363
|
+
applyPlaybackRate(a);
|
|
1364
1364
|
}
|
|
1365
1365
|
catch (e) { }
|
|
1366
|
-
}, [playbackRate,
|
|
1366
|
+
}, [playbackRate, applyPlaybackRate]);
|
|
1367
1367
|
// ๋ฌธ์ฅ(๋ฉ์ธ) ๋ณผ๋ฅจ ๋ฐ์ โ ๋ฌธ์ ์๋ฆฌ ๋ณผ๋ฅจ(effectiveSpeechVolume = ์ ์ฒด ์๋ฆฌ). setElementVolume ์ด ๊ทธ๋ํ์ ํ์ด
|
|
1368
1368
|
// ์์๋ฉด GainNode, ์๋๋ฉด ์์ volume ์ ๊ฑด๋ค(iOS ์ค๊ธฐ๊ธฐ๋ ์์ volume ์ด ์ ๋จน๋๋ค). ์ฌ์ ์ค์ด๋ฉด ์์๊ณผ ๊ฐ์ 150ms ๋จํ๋ก ์ฎ๊ธฐ๊ณ , ๋ฉ์ถฐ ์์ผ๋ฉด ๋ฐ๋ก ๊ฑด๋ค.
|
|
1369
1369
|
// ๋ฐฑ๊ทธ๋ผ์ด๋ ํญ์์๋ RAF ๊ฐ ๋ฉ์ถฐ ๋จํ๊ฐ ์ค๊ฐ์ ์ค ์ ์์ผ๋ฏ๋ก ๋๊ฐ์ ํ์ด๋จธ๋ก ํ ๋ฒ ๋ ๋ฐ๋๋ค.
|
|
@@ -1374,7 +1374,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1374
1374
|
return;
|
|
1375
1375
|
const target = Math.max(0, Math.min(1, speechVolume));
|
|
1376
1376
|
const ramp = speechRampRef.current;
|
|
1377
|
-
const
|
|
1377
|
+
const detach = () => {
|
|
1378
1378
|
if (ramp.raf !== null) {
|
|
1379
1379
|
cancelAnimationFrame(ramp.raf);
|
|
1380
1380
|
ramp.raf = null;
|
|
@@ -1384,7 +1384,7 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1384
1384
|
ramp.timer = null;
|
|
1385
1385
|
}
|
|
1386
1386
|
};
|
|
1387
|
-
|
|
1387
|
+
detach();
|
|
1388
1388
|
const from = getElementVolume(a);
|
|
1389
1389
|
if (a.paused || Math.abs(from - target) < 0.001 || typeof requestAnimationFrame !== 'function') {
|
|
1390
1390
|
try {
|
|
@@ -1403,11 +1403,11 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1403
1403
|
ramp.raf = t < 1 ? requestAnimationFrame(step) : null;
|
|
1404
1404
|
};
|
|
1405
1405
|
ramp.raf = requestAnimationFrame(step);
|
|
1406
|
-
ramp.timer = setTimeout(() => {
|
|
1406
|
+
ramp.timer = setTimeout(() => { detach(); try {
|
|
1407
1407
|
setElementVolume(a, target);
|
|
1408
1408
|
}
|
|
1409
1409
|
catch (e) { } }, 200);
|
|
1410
|
-
return
|
|
1410
|
+
return detach;
|
|
1411
1411
|
}, [speechVolume]);
|
|
1412
1412
|
// fill (์งํ ๋ง์ปค) ํด๋์ค ๋ฐ ์ด๊ธฐ ์์น ๊ณ์ฐ โ RAF ๋ฃจํ์์ .style.left๋ฅผ ์ง์ ๊ฐฑ์ ํฉ๋๋ค.
|
|
1413
1413
|
const isFillHolding = fillCompletionPhase === 'hold';
|
|
@@ -1426,12 +1426,12 @@ export default function TrainingStepAudioPlayer({ btnLabel, soundSrc, bucketPath
|
|
|
1426
1426
|
๋๋ฅด๋ฉด ์๋ฆฌ๋ฅผ ๋ฐ์ผ๋ฌ ๊ฐ๊ฑฐ๋ "๊ฐ์ ธ์ค์ง ๋ชปํ์ต๋๋ค" ๋ฅผ ๋์ ๋ค โ ๋ง์ฐ์ค์ ๊ฐ๊ฒ ํค๋ณด๋๋ ๋ง๋๋ค */
|
|
1427
1427
|
disabled: isDisabled,
|
|
1428
1428
|
// ํค๋ณด๋๋ก ๋๋ ์ ๋๋ง ์ด์ ์ ์ฎ๊ธด๋ค โ ๋ง์ฐ์ค๋ก ๋๋ฅธ ์ฌ๋์๊ฒ '์ฌ์ / ๋ฉ์ถค' ์ด์ ํดํ์ด ๋จ์ง ์๊ฒ
|
|
1429
|
-
onClick: () => {
|
|
1429
|
+
onClick: () => { moveFocusToListenRef.current = wasKeyboardPress(); hideListenTip(); handleToggle(); }, "aria-label": typeof label === 'string' ? label : t('training.listen', '๋ฃ๊ธฐ'), onMouseEnter: showListenTip, onMouseLeave: hideListenTip,
|
|
1430
1430
|
// ํค๋ณด๋๋ก ์ด ๋ฒํผ์ ์ค๋ฉด ๋ง์ฐ์ค์ ๋์ผํ๊ฒ ์๋ด๋ฅผ ๋์ด๋ค
|
|
1431
1431
|
onFocus: showListenTip, onBlur: hideListenTip, children: _jsx("span", { ref: listenBtnRef, className: "audio-player__button-triangle-wrap", children: _jsx("svg", { className: "audio-player__button-triangle", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { ref: triPathRef, d: "M13.2 7.7 L16.5 9.85 Q20.2 12 16.5 14.15 L10 18.3 Q7 19.7 7 16.3 L7 7.7 Q7 4.3 10 5.7 L13.2 7.7" }) }) }) })) : null, listenBtnRect && (_jsx(InfoTooltip, { anchorRect: listenBtnRect, text: typeof label === 'string' && label ? label : t('training.listenLabel', '๋ฌธ์ฅ ๋ฃ๊ธฐ'), offset: 8 })), _jsxs("div", { className: `audio-player__play ${stateLocal === "playMode" || displayedPlaying ? "is-visible" : ""}`, inert: !(stateLocal === "playMode" || displayedPlaying) || undefined, children: [_jsxs("div", { ref: btnsRef, className: "audio-player__buttons", children: [_jsx(Button, { htmlType: "button", size: 32, showTooltipInfo: true, type: "dot", shape: "pill", useBorder: false, icon: "skip-back", onClick: handlePrev, ariaLabel: t('training.prev', '์ด์ '), width: "60px", variant: "transparent-white", "aria-hidden": "true", tabIndex: -1, disabled: stateLocal !== 'playMode' }), _jsx(Button, { htmlType: "button", size: 32, showTooltipInfo: true, type: "dot", shape: "pill", useBorder: false, icon: displayedPlaying ? "pause" : "play", onClick: handleToggle, ariaLabel: t('common.playPause', '์ฌ์ / ๋ฉ์ถค'), "aria-pressed": displayedPlaying, width: "60px", variant: "transparent-white", disabled: stateLocal !== 'playMode' }), _jsx(Button, { htmlType: "button", size: 32, showTooltipInfo: true, type: "dot", shape: "pill", useBorder: false, icon: "square", onClick: handleStop, ariaLabel: t('training.stop', '์ ์ง'), width: "60px", variant: "transparent-white", "aria-hidden": "true", tabIndex: -1, disabled: stateLocal !== 'playMode' }), sourceButtonCount > 1 ? (_jsx("div", { ref: sourceButtonsRef, className: "audio-player__source-buttons", children: Array.from({ length: sourceButtonCount }).map((_, idx) => (_jsx(Button, { htmlType: "button", size: 32, type: "dot", shape: "pill", useBorder: false, label: String(idx + 1), onClick: () => { try {
|
|
1432
1432
|
handleJumpTo(idx);
|
|
1433
1433
|
}
|
|
1434
|
-
catch (e) { } }, ariaLabel: t('training.sourcePlay', { n: idx + 1 }), variant: "transparent-white", selected:
|
|
1434
|
+
catch (e) { } }, ariaLabel: t('training.sourcePlay', { n: idx + 1 }), variant: "transparent-white", selected: showRange && currentIndex === idx, disabled: stateLocal !== 'playMode' || !(Array.isArray(sources) && sources[idx]), showTooltipInfo: true }, idx))) })) : null, (!Array.isArray(sources) || sources.length <= 1) ? (_jsx(Button, { htmlType: "button", size: 32, showTooltipInfo: true, type: "dot", shape: "pill", useBorder: false, icon: "repeat", onClick: handleReplay, ariaLabel: t('common.replay', '๋ค์๋ฃ๊ธฐ'), width: "60px", variant: "transparent-white", disabled: stateLocal !== 'playMode' })) : null, _jsx(Button, { htmlType: "button", size: 32, showTooltipInfo: true, type: "dot", shape: "pill", useBorder: false, icon: "skip-forward", onClick: handleNext, ariaLabel: t('training.next', '๋ค์'), width: "60px", variant: "transparent-white", "aria-hidden": true, tabIndex: -1, disabled: stateLocal !== 'playMode' }), _jsx(Button, { htmlType: "button", size: 32, showTooltipInfo: true, type: "dot", shape: "pill", useBorder: false, icon: "check", onClick: handleEnd, ariaLabel: t('common.done', '์๋ฃ'), width: "60px", variant: "transparent-white", disabled: stateLocal !== 'playMode' })] }), _jsxs("div", { className: "audio-player__slider", children: [_jsx("div", { className: fillClass, ref: fillRef, style: { left: `${fillLeftPercent}%` }, "aria-hidden": "true" }), (showSoundWaveForm || showNoiseWaveForm) && (_jsx(WaveForm, { segments: currentSegments, progress: progress, totalDuration: totalDuration, waveProgressRef: waveProgressRef, showSoundWaveForm: showSoundWaveForm,
|
|
1435
1435
|
/* ๋ธ๋ ํํ์ ๊ทธ๋ฆฐ๋ค((74)) โ 2์ด ๋จ์ด์ ์๋ค 1์ด๋ฉด 4์ด๊ฐ ํ ํญ์ ๋ค์ด๊ฐ๋ค */
|
|
1436
1436
|
leadInMs: leadInMs, tailMs: tailMs, trackDurationSec: sources?.[currentIndex]?.duration ?? 0, noiseSrc: (showNoiseWaveForm && noisePlayer?.settings.enabled && noiseVolume > 0) ? activeNoiseUrl : null, noiseRatio: noisePlayer ? noiseRatioOf(noisePlayer.settings) : 0, noiseSpeed: noisePlayer?.settings.speed ?? 1,
|
|
1437
1437
|
/* ๊ทธ๋ฆผ์ "๋ฌธ์ฅ = ๊ธฐ์ค" โ ๋ฌธ์ ์๋ฆฌ(MรทR)๊ฐ ์๋๋ผ ์ ์ฒด ์๋ฆฌ M ์ผ๋ก ๋ฌธ์ฅ์ ๊ทธ๋ฆฌ๊ณ ์์์ R ๋ฐฐ๋ก ํค์ด๋ค */
|