-
Notifications
You must be signed in to change notification settings - Fork 0
Fix word loop conflicts during app navigation and audio playback #208
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
Open
Aiuanyu
wants to merge
1
commit into
main
Choose a base branch
from
fix-word-loop-conflicts-5861562139682209968
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -174,7 +174,7 @@ function toggleSearchAccordion(clickedButton, line) { | |
|
|
||
| // Pause autoplay if it's running | ||
| const stopButton = document.getElementById('stopBtn'); | ||
| if (isPlaying && stopButton) { | ||
| if (stopButton) { | ||
| stopButton.click(); | ||
| } | ||
|
|
||
|
|
@@ -1094,6 +1094,13 @@ function showPronunciationPopup( | |
| if (playButton) { | ||
| playButton.addEventListener('click', (e) => { | ||
| e.stopPropagation(); // 保持 stopPropagation 以避免 headerBtn 也響應 | ||
|
|
||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
| if (isPlaying) { | ||
| stopPlayback(); | ||
| } | ||
|
|
||
| const header = playButton.closest('.accordion-header'); | ||
| const panel = header ? header.nextElementSibling : null; | ||
| if (header && panel && !header.classList.contains('active')) { | ||
|
|
@@ -2415,6 +2422,9 @@ function initializeAppUI() { | |
| } | ||
|
|
||
| function performSearch(page = 1, itemsPerPage = 50) { | ||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
|
|
||
| const selectedDialect = document.querySelector( | ||
| '#search-popup input[name="dialect"]:checked', | ||
| ).value; | ||
|
|
@@ -3444,6 +3454,24 @@ function initializeAppUI() { | |
|
|
||
| document.addEventListener('keydown', globalKeydownHandler); | ||
|
|
||
| // --- 【新增】全域音檔衝突處理:播放任何音檔時,若正在單詞循環則停止之 --- | ||
| document.addEventListener( | ||
| 'play', | ||
| (event) => { | ||
| if (event.target.tagName === 'AUDIO' && isSingleWordLooping) { | ||
| if ( | ||
| event.target !== singleLoopingAudio.word && | ||
| event.target !== singleLoopingAudio.sentence | ||
| ) { | ||
| console.log('偵測到其他音檔播放,停止單詞循環。'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| stopSingleWordLoop(); | ||
| g_mainPlaybackIndexBeforeLoop = null; | ||
| } | ||
| } | ||
| }, | ||
| true, | ||
| ); | ||
|
|
||
| document.addEventListener('click', (event) => { | ||
| if (event.target.tagName === 'BUTTON' || event.target.closest('button')) { | ||
| const button = | ||
|
|
@@ -3730,6 +3758,10 @@ function initializeAppUI() { | |
| // --- generate() 函式從這裡開始 --- | ||
| function generate(content, initialCategory = null, targetRowId = null) { | ||
| console.log('Generate called for:', content.name); | ||
|
|
||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
|
|
||
| currentActiveDialectLevelFullName = getFullLevelName(content.name); | ||
| g_currentLevelData = [...content.content]; // Create a mutable copy to be sorted | ||
|
|
||
|
|
@@ -3876,6 +3908,9 @@ function initializeAppUI() { | |
| ) { | ||
| const contentContainer = document.getElementById('generated'); | ||
|
|
||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
|
|
||
| // 1. Reset global state for the new category | ||
| g_currentDialectInfo = dialectInfo; | ||
| g_currentCategory = category; | ||
|
|
@@ -4157,6 +4192,8 @@ function initializeAppUI() { | |
|
|
||
| // --- Auto Bookmark Mode: Add play event listener with proper cleanup --- | ||
| const wordPlayHandler = () => { | ||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
| const autoBookmarkEnabled = | ||
| localStorage.getItem('autoBookmarkMode') === 'true'; | ||
| if (autoBookmarkEnabled && dialectInfo.腔 && dialectInfo.級) { | ||
|
|
@@ -4235,6 +4272,8 @@ function initializeAppUI() { | |
|
|
||
| // --- Auto Bookmark Mode: Add play event listener with proper cleanup --- | ||
| const sentencePlayHandler = () => { | ||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
| const autoBookmarkEnabled = | ||
| localStorage.getItem('autoBookmarkMode') === 'true'; | ||
| if (autoBookmarkEnabled && dialectInfo.腔 && dialectInfo.級) { | ||
|
|
@@ -4396,6 +4435,9 @@ function initializeAppUI() { | |
| return; | ||
| } | ||
|
|
||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
|
|
||
| // 重設狀態 | ||
| // 【新增此行】用當前時間戳記產生一個獨一無二的 ID | ||
| playbackSessionId = Date.now(); | ||
|
|
@@ -4622,6 +4664,7 @@ function initializeAppUI() { | |
| currentAudio = null; | ||
| currentAudioIndex = 0; | ||
| removeNowPlaying(); | ||
| stopSingleWordLoop(); // 【新增】 | ||
| updateMediaSession(null); // --- 【整合 Media Session】 --- | ||
|
|
||
| const pauseResumeButton = document.getElementById('pauseResumeBtn'); | ||
|
|
@@ -4898,6 +4941,8 @@ function initializeAppUI() { | |
|
|
||
| if (stopButton) { | ||
| stopButton.onclick = function () { | ||
| stopSingleWordLoop(); // 【新增】 | ||
| g_mainPlaybackIndexBeforeLoop = null; // 【新增】 | ||
| if (isPlaying) { | ||
| stopPlayback(); | ||
| } | ||
|
|
@@ -5634,7 +5679,7 @@ function toggleAccordion(event, line, dialectInfo) { | |
|
|
||
| // Pause autoplay if it's running | ||
| const stopButton = document.getElementById('stopBtn'); | ||
| if (isPlaying && stopButton) { | ||
| if (stopButton) { | ||
| stopButton.click(); | ||
| } | ||
|
|
||
|
|
||
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "status": "failed", | ||
| "failedTests": [ | ||
| "918302d1937b88b99808-4d1eeff0c71276426eba", | ||
| "918302d1937b88b99808-34f5346cfa950e8fc8e0" | ||
| ] | ||
| } |
144 changes: 144 additions & 0 deletions
144
test-results/loop-Single-word-loop-should-stop-when-searching/error-context.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # Page snapshot | ||
|
|
||
| ```yaml | ||
| - generic [active] [ref=e1]: | ||
| - generic [ref=e2]: | ||
| - generic [ref=e3]: | ||
| - heading "logo客源翠 HakSpring" [level=2] [ref=e4]: | ||
| - link "logo客源翠 HakSpring" [ref=e5] [cursor=pointer]: | ||
| - /url: / | ||
| - img "logo" [ref=e6] | ||
| - text: 客源翠 HakSpring | ||
| - heading "你个客援隊/客研隊。詞典+分類學習" [level=4] [ref=e7] | ||
| - heading "擇進前个進度" [level=3] [ref=e8]: | ||
| - combobox [ref=e9] [cursor=pointer]: | ||
| - option "擇進前个進度" [disabled] [selected] | ||
| - button "看說明文件" [ref=e10] [cursor=pointer]: | ||
| - generic [ref=e11]: | ||
| - button "客語羅馬字轉換工具" [ref=e12] [cursor=pointer]: | ||
| - generic [ref=e13]: | ||
| - textbox "查語詞" [ref=e15] | ||
| - group [ref=e17]: | ||
| - generic "認證詞彙學習主控板" [ref=e18] [cursor=pointer] | ||
| - paragraph [ref=e19]: | ||
| - text: 先擇腔調摎級別, | ||
| - generic [ref=e20]: | ||
| - text: 四縣: | ||
| - link "基礎" [ref=e22] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "初級" [ref=e24] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中級" [ref=e26] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中高" [ref=e28] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "高級" [ref=e30] [cursor=pointer]: | ||
| - /url: "#" | ||
| - generic [ref=e31]: | ||
| - text: 海陸: | ||
| - link "基礎" [ref=e33] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "初級" [ref=e35] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中級" [ref=e37] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中高" [ref=e39] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "高級" [ref=e41] [cursor=pointer]: | ||
| - /url: "#" | ||
| - generic [ref=e42]: | ||
| - text: 大埔: | ||
| - link "基礎" [ref=e44] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "初級" [ref=e46] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中級" [ref=e48] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中高" [ref=e50] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "高級" [ref=e52] [cursor=pointer]: | ||
| - /url: "#" | ||
| - generic [ref=e53]: | ||
| - text: 饒平: | ||
| - link "基礎" [ref=e55] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "初級" [ref=e57] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中級" [ref=e59] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中高" [ref=e61] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "高級" [ref=e63] [cursor=pointer]: | ||
| - /url: "#" | ||
| - generic [ref=e64]: | ||
| - text: 詔安: | ||
| - link "基礎" [ref=e66] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "初級" [ref=e68] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中級" [ref=e70] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "中高" [ref=e72] [cursor=pointer]: | ||
| - /url: "#" | ||
| - link "高級" [ref=e74] [cursor=pointer]: | ||
| - /url: "#" | ||
| - generic [ref=e75]: | ||
| - text: 再擇類別: | ||
| - generic [ref=e76] [cursor=pointer]: | ||
| - radio "人體與醫療" [ref=e77] | ||
| - text: 人體與醫療 | ||
| - generic [ref=e78] [cursor=pointer]: | ||
| - radio "心理活動與感覺" [ref=e79] | ||
| - text: 心理活動與感覺 | ||
| - generic [ref=e80] [cursor=pointer]: | ||
| - radio "代詞" [ref=e81] | ||
| - text: 代詞 | ||
| - generic [ref=e82] [cursor=pointer]: | ||
| - radio "外在活動與動作" [ref=e83] | ||
| - text: 外在活動與動作 | ||
| - generic [ref=e84] [cursor=pointer]: | ||
| - radio "生物" [ref=e85] | ||
| - text: 生物 | ||
| - generic [ref=e86] [cursor=pointer]: | ||
| - radio "自然與景觀" [ref=e87] | ||
| - text: 自然與景觀 | ||
| - generic [ref=e88] [cursor=pointer]: | ||
| - radio "事物狀態與變化" [ref=e89] | ||
| - text: 事物狀態與變化 | ||
| - generic [ref=e90] [cursor=pointer]: | ||
| - radio "居家生活" [ref=e91] | ||
| - text: 居家生活 | ||
| - generic [ref=e92] [cursor=pointer]: | ||
| - radio "抽象概念與形容" [ref=e93] | ||
| - text: 抽象概念與形容 | ||
| - generic [ref=e94] [cursor=pointer]: | ||
| - radio "法律、政治與軍事" [ref=e95] | ||
| - text: 法律、政治與軍事 | ||
| - generic [ref=e96] [cursor=pointer]: | ||
| - radio "社會關係與行為" [ref=e97] | ||
| - text: 社會關係與行為 | ||
| - generic [ref=e98] [cursor=pointer]: | ||
| - radio "時空與情狀副詞" [ref=e99] | ||
| - text: 時空與情狀副詞 | ||
| - generic [ref=e100] [cursor=pointer]: | ||
| - radio "特殊詞類" [ref=e101] | ||
| - text: 特殊詞類 | ||
| - generic [ref=e102] [cursor=pointer]: | ||
| - radio "通訊、建設與交通" [ref=e103] | ||
| - text: 通訊、建設與交通 | ||
| - generic [ref=e104] [cursor=pointer]: | ||
| - radio "歲時祭儀、習俗與宗教" [ref=e105] | ||
| - text: 歲時祭儀、習俗與宗教 | ||
| - generic [ref=e106] [cursor=pointer]: | ||
| - radio "數詞量詞" [ref=e107] | ||
| - text: 數詞量詞 | ||
| - generic [ref=e108] [cursor=pointer]: | ||
| - radio "職業與經濟" [ref=e109] | ||
| - text: 職業與經濟 | ||
| - generic [ref=e110] [cursor=pointer]: | ||
| - radio "藝文與教育" [ref=e111] | ||
| - text: 藝文與教育 | ||
| - text: | ||
| - text: | ||
| - text: | ||
| ``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two lines for resetting the single-word loop state are repeated in at least 8 other places in this file. To improve code maintainability and adhere to the DRY (Don't Repeat Yourself) principle, I recommend extracting this logic into a new helper function.
You could define a function like this, for example after the
stopSingleWordLoopfunction definition:Then, you can replace this block and all similar occurrences with a single call:
resetSingleWordLoopState();.