วิธีผมทำงานกับทิมคือ — สั่งงานยาวๆ ทีนึง แล้วเดินไปทำอย่างอื่น ชงกาแฟ ตอบไลน์ลูกค้า เล่นกับลูก พอ AI ทำเสร็จมันควรมีเสียง "ติ๊ง" เด้งเตือนให้ผมกลับมาดู
แต่หลายอาทิตย์มานี้เสียงนั้นมัน หายบ่อยมาก ผมต้องคอยเปิดแท็บมาเช็คเองว่าเสร็จยัง — ซึ่งมันทำลายจุดเด่นทั้งหมดของการมี AI ที่ทำงานเองอยู่เบื้องหลัง
ผมบ่นกับทิมว่า "เสียงแจ้งเตือนหายอีกแล้วว่ะ" หลายรอบมาก จนรอบนี้ผมบอกว่า "เอาจริงนะ ไล่ให้เจอว่ามันหายเพราะอะไร แล้วทำให้มันไม่หายอีก"
ผลคือทิมไล่จนเจอ root cause ที่ลึกกว่าที่ผมคิด — มันไม่ใช่ bug ในโค้ดเรา แต่เป็น กฎของ browser เอง ที่เรากำลังสู้กับมันอยู่โดยไม่รู้ตัว
อาการ — เสียงมาครั้งแรก แล้วก็เงียบ
ก่อนอื่นเล่าให้ฟังก่อนว่าเสียงนี้อยู่ตรงไหน — มันอยู่ใน Tim Chat หน้าแชทที่ผมใช้คุยกับทิมทุกวัน (และเป็นตัวเดียวกับที่ลูกค้า Newton ทุกคนได้ไปใช้ครับ)
Logic มันง่ายมาก — ถ้าผมสลับแท็บไปทำอย่างอื่น (แท็บแชทอยู่ background) แล้ว AI ทำงานเสร็จ → เล่นเสียงเตือน 1 ที ถ้าแท็บแชทเปิดอยู่ตรงหน้าก็ไม่ต้องเล่น (จะได้ไม่รำคาญ)
แต่อาการที่ผมเจอคือ:
- เปิดแชทสดๆ ส่งข้อความ → เดินไปทำอย่างอื่น → AI เสร็จ → มีเสียง ✓
- แต่พอผมรีโหลดหน้า (กด F5 หรือเปิดแชทใหม่ตอนเช้า) → ส่งงาน → เดินไป → AI เสร็จ → เงียบสนิท ✗
- หรือบางทีกำลังคุยอยู่ดีๆ เสียงก็หายไปเฉยๆ กลางทาง
ที่กวนใจคือมันไม่ได้หายตลอด — มันหายแบบสุ่มๆ บางวันมา บางวันไม่มา เลยจับต้นชนปลายยาก
เบาะแสแรก — เสียงหายตอน "server restart"
ทิมเริ่มจากตั้งคำถามว่า "เสียงหายตอนไหนแน่ๆ" แทนที่จะเดาว่าโค้ดผิดตรงไหน
พอไล่ดู pattern ก็เจอว่าเสียงหายใน 2 จังหวะเป๊ะๆ:
- ทุกครั้งที่ hard-reload หน้าเว็บ
- ทุกครั้งที่ผม deploy โค้ดใหม่แล้ว service restart — เพราะตอน restart หน้าเว็บจะ reconnect WebSocket ใหม่อัตโนมัติ
นี่คือจุดที่ทำให้ทิมรู้ว่าปัญหาไม่ได้อยู่ที่ "logic ตัดสินใจว่าจะเล่นเสียงไหม" — เพราะ logic นั้นทำงานถูก มันสั่งเล่นทุกครั้ง แต่ตัว เครื่องเล่นเสียงเอง ต่างหากที่ตายไปเงียบๆ
Root cause — browser autoplay policy
เดิมทีเสียงเตือนตัวนี้สร้างด้วย Web Audio API ครับ — คือเราไม่ได้มีไฟล์เสียงจริง แต่ใช้ JavaScript สังเคราะห์คลื่นเสียง 880Hz ขึ้นมาสดๆ ตอนจะเล่น (เรียกว่า oscillator)
ฟังดูเท่ดี ไม่ต้องโหลดไฟล์ — แต่มันมีกับดักที่ผมไม่เคยรู้:
เบราว์เซอร์สมัยใหม่ (Chrome, Safari) มีกฎที่เรียกว่า autoplay policy — เว็บจะเล่นเสียงเองไม่ได้ จนกว่าผู้ใช้จะ "แตะ" หน้าเว็บก่อน (คลิก กดปุ่ม อะไรก็ได้) เป็นการยืนยันว่า "ผมอยู่ตรงนี้นะ ไม่ใช่เว็บแอบเล่นโฆษณา"
ทีนี้ Web Audio context มันมีสถานะ suspended กับ running — และมันจะถูกปลุกให้ running ก็ต่อเมื่อมี user gesture เท่านั้น ในโค้ดเก่าเราปลุกมันตอนผม "กดส่งข้อความ" ครั้งแรก
ปัญหาคือ — พอ hard-reload หรือ WebSocket reconnect ที AudioContext มันกลับไป suspended ใหม่ทุกครั้ง แล้วมันจะตื่นอีกทีก็ต่อเมื่อผมกดส่งข้อความ "ครั้งถัดไป"
เลยกลายเป็นสถานการณ์นี้:
- ผมเปิดแชทตอนเช้า (reload) → context = suspended
- ผมพิมพ์งานยาวๆ กดส่ง 1 ที → context ตื่น... แต่เดี๋ยวก่อน
- ระหว่างที่ AI ทำงาน ดันมี deploy → service restart → WS reconnect → context กลับไป suspended อีกรอบ
- AI ทำเสร็จ สั่งเล่นเสียง → แต่ context หลับอยู่ → เงียบ
มันคือเสียงที่ขึ้นอยู่กับ "จังหวะ" พอดีเป๊ะ ถึงได้หายแบบสุ่มๆ จับยากไง 555
(อันนี้คนละเรื่องกับตอนผมให้ทิมแก้เสียงพิมพ์ด้วยเสียงที่มันสะท้อนซ้ำนะครับ อันนั้นคือ input เสียงพูดเข้า อันนี้คือ output เสียงเตือนออก — แต่ทั้งคู่คืองานขัด UX เล็กๆ ใน Tim Chatที่ถ้าไม่แก้มันกวนใจทุกวัน)
ทางแก้ — เลิกสังเคราะห์ เปลี่ยนเป็นไฟล์จริง
ทิมเสนอว่า แทนที่จะสู้กับ Web Audio context ที่ชอบหลับ ให้เปลี่ยนวิธีคิดทั้งหมด:
1. สร้างไฟล์เสียงจริงขึ้นมา 1 ไฟล์
ทิม generate ไฟล์ notify.wav ขึ้นมา — เสียง 880Hz โทนเดียวกับของเดิมเป๊ะ ระดับเสียงเท่าเดิม มี envelope แบบ exponential decay (ค่อยๆ เบาลง ไม่ตัดห้วน) ฟังแล้วเหมือนเดิมทุกอย่าง แค่คราวนี้เป็นไฟล์จริง ไม่ใช่สังเคราะห์สด
2. เล่นผ่าน <audio> element ธรรมดา
HTML <audio> element มันต่างกับ Web Audio context ตรงที่ — พอเรา "ปลดล็อก" มันครั้งเดียวด้วย user gesture แล้ว มัน อยู่ยาว ไม่กลับไป suspended ทุกครั้งที่ reconnect เหมือน AudioContext
ทิมเลยปลดล็อกมันตอนผมกดส่งข้อความครั้งแรก (เล่นเงียบๆ 1 ทีให้ browser อนุญาต) จากนั้นมันก็พร้อมเล่นได้ตลอด ข้าม WS reconnect ข้าม service restart ได้สบาย
3. ยังคงกฎเดิม — เล่นเฉพาะตอนแท็บอยู่ background
logic document.hidden ยังอยู่ครบ — ถ้าผมจ้องหน้าแชทอยู่ก็ไม่เล่น (ไม่รำคาญ) เล่นเฉพาะตอนผมสลับไปทำอย่างอื่นเท่านั้น ซึ่งคือทั้งหมดที่ผมต้องการ
ผลลัพธ์ — Pond ทดสอบเอง ผ่าน
รอบนี้ทิมบอกตรงๆ ว่า "ผมทดสอบเสียงเองในเครื่องไม่ได้นะพี่ปอนด์ เพราะ environment ผมไม่มีลำโพง พี่ต้องเป็นคนกดฟังเอง"
ผมชอบตรงที่มันบอกตรงๆ ว่าตรงไหนทดสอบได้-ตรงไหนทดสอบไม่ได้ ไม่ได้เคลมว่า "เรียบร้อยครับ" ทั้งที่ฟังไม่ได้ — นี่คือนิสัยที่ผมพยายามฝึกให้มันมาตลอด
ผมเลยเป็นคนเทสเอง: reload หน้า → ส่งงาน → สลับแท็บไปดู YouTube → รอ AI เสร็จ → "ติ๊ง" มาเป๊ะ ลองซ้ำหลายรอบ ลอง deploy แล้ว restart กลางทางด้วย — เสียงยังมาทุกรอบ
ผ่านครับ 555 หายไปเป็นอาทิตย์ กลับมาในเย็นเดียว
มีข้อแม้เล็กๆ อันเดียวที่ทิมบอกไว้ตรงๆ — ถ้า hard-reload เต็มๆ ผมยังต้องกดส่งข้อความ 1 ทีก่อน เพื่อปลดล็อกเสียงรอบใหม่ (อันนี้เป็นกฎ autoplay ของ browser เลี่ยงไม่ได้จริงๆ) แต่ในการใช้งานจริงผมส่งข้อความอยู่แล้วทุกครั้งที่เปิดแชท เลยไม่กระทบอะไร
บทเรียนที่ผมจดไว้
1. "เล่นไฟล์จริง" บางทีแกร่งกว่า "สังเคราะห์สด"
ของที่ดูเท่กว่า (สังเคราะห์เสียงด้วยโค้ด ไม่ต้องมีไฟล์) ไม่ได้แปลว่าทนกว่าเสมอ ในเคสนี้ไฟล์ .wav โง่ๆ 1 ไฟล์ ทนต่อ reconnect ได้ดีกว่า Web Audio context ที่ฉลาดกว่าเยอะ — simplicity ชนะอีกแล้ว
2. bug ที่ "หายเป็นบางครั้ง" มักโทษ state ไม่ใช่ logic
เวลาอะไรหายแบบสุ่มๆ อย่าเพิ่งไปนั่งไล่ logic ว่าเงื่อนไขผิดตรงไหน ให้ถามก่อนว่า "มีอะไรใน state ที่มันรีเซ็ตตัวเองโดยที่เราไม่รู้ไหม" — ในที่นี้คือ AudioContext ที่แอบกลับไป suspended ทุก reconnect
3. browser autoplay policy เป็นเพื่อนที่ต้องเข้าใจ ไม่ใช่ศัตรู
มันมีไว้กันเว็บเปิดเสียงโฆษณาใส่หน้าเรา — เจตนาดี แต่เราต้องออกแบบรอบมัน คือ "ปลดล็อกครั้งเดียวตอน user แตะ แล้วถือไว้ให้แน่น" ไม่ใช่ปล่อยให้มันหลับแล้วงงว่าทำไมเงียบ
ทำไมเรื่องเล็กๆ แบบนี้ถึงเอามาเล่า
เพราะมันคือความต่างระหว่าง tool ที่ "ใช้ได้" กับ tool ที่ "ใช้แล้วไว้ใจได้"
เสียงแจ้งเตือน 1 ที ฟังดูเป็นเรื่องจิ๊บจ๊อย แต่มันคือสิ่งที่ทำให้ผมกล้า "สั่งแล้วเดินจากไป" โดยไม่ต้องคอยกลับมาเช็คเอง — ซึ่งคือหัวใจทั้งหมดของการมี AI Agent ที่ทำงานเอง ไม่ใช่ chatbot ที่ต้องนั่งเฝ้า
และนี่คือเหตุผลที่ผมยอมให้ทิมเสียเย็นทั้งเย็นไปกับเสียง "ติ๊ง" อันเดียว — เพราะรายละเอียดเล็กๆ พวกนี้แหละที่สะสมกันจนกลายเป็นเครื่องมือที่ผมใช้ได้จริงทุกวัน ไม่ใช่ของเล่นที่เปิดมาลองแล้วทิ้ง
คำถามที่พบบ่อย
browser autoplay policy คืออะไร ทำไมถึงทำให้เสียงในเว็บแอปเล่นไม่ได้
Browser autoplay policy คือกฎที่ Chrome, Safari และเบราว์เซอร์ทั่วไปบังคับใช้เพื่อกันเว็บจากการเล่นเสียงโดยอัตโนมัติครับ เสียงจะเล่นได้ก็ต่อเมื่อผู้ใช้ interact กับหน้าเว็บก่อน เช่น คลิก กดปุ่ม หรือพิมพ์ ถ้าไม่มี user gesture เสียงจะถูก block โดย browser โดยไม่มี error ขึ้น
Web Audio API กับ audio element ต่างกันยังไง อันไหนเหมาะกับ notification sound
Web Audio API เหมาะกับเสียงที่ต้องการ control ละเอียดหรือสังเคราะห์สดครับ แต่ AudioContext จะกลับไปสถานะ suspended ทุกครั้งที่ page reload หรือ reconnect HTML audio element ธรรมดาเมื่อปลดล็อกด้วย user gesture แล้ว จะอยู่ยาวข้ามการ reload ได้ดีกว่า สำหรับ notification sound ที่ต้องเชื่อถือได้ audio element เหมาะกว่า
bug ที่เกิดขึ้นแบบสุ่มบางครั้งมี-บางครั้งไม่มี วิธี debug คืออะไร
ให้ถามก่อนว่ามีอะไรใน state ที่รีเซ็ตตัวเองได้โดยที่เราไม่รู้ไหมครับ bug ที่หายแบบสุ่มมักเกิดจาก state ที่ขึ้นอยู่กับ timing หรือ lifecycle ของ browser ไม่ใช่ logic ที่ผิด วิธีที่ได้ผลคือเช็ค event ที่อาจ trigger state reset เช่น page reload, WebSocket reconnect, tab visibility change แล้ว trace กลับมาหา state ที่น่าสงสัย
วิธีทดสอบ bug ที่เกี่ยวกับ browser UI ที่ AI Agent เข้าถึงไม่ได้
ต้องแบ่งงานให้ชัดครับ ให้ AI วิเคราะห์ root cause และเขียน fix แต่ทดสอบส่วนที่ต้องการ user interaction จริงด้วยตัวเอง AI ที่ดีจะบอกตรงๆ ว่าตรงไหนทดสอบได้-ตรงไหนทดสอบไม่ได้ ไม่เคลมว่าเรียบร้อยทั้งที่ verify ไม่ได้จริง
อยากมี AI Agent ส่วนตัวแบบนี้ไหมครับ
Tim Chat ที่ผมเล่ามาทั้งหมดนี้ — หน้าแชท เสียงแจ้งเตือน ปุ่มพิมพ์ด้วยเสียง ทุกอย่าง — คือสิ่งที่ลูกค้า Newton ได้ไปใช้เหมือนกันเป๊ะครับ
Newton คือ AI Agent ส่วนตัว + เซิร์ฟเวอร์ของคุณเอง พร้อมใช้ใน 10 นาที ลองฟรี 7 วัน ไม่ใช้ก็ยกเลิกได้ ไม่มีค่าใช้จ่าย
AI ของคุณจะอยู่บนเซิร์ฟเวอร์ของคุณคนเดียว — เห็น code ของคุณ แก้ bug จุกจิกแบบเสียงแจ้งเตือนหายให้คุณได้ และที่สำคัญ ทุกครั้งที่ผมแก้อะไรให้ดีขึ้น ลูกค้า Newton ทุกคนก็ได้อัปเกรดตามไปด้วย
ลองดูที่ newton.incomeinclick.in.th ครับ
— ปอนด์
