App เป่าเทียน
App เป่าเทียนวันเกิด Blow Detection Microphone Web Audio API Animation Canvas CSS Confetti Mobile React Native Flutter Production
| Technology | Platform | Blow Detection | Animation | Difficulty |
|---|---|---|---|---|
| Web Audio API + Canvas | Web Browser | AnalyserNode | Canvas/CSS | ปานกลาง |
| React + Web Audio | Web (React) | Custom Hook | Framer Motion | ปานกลาง |
| React Native + Expo | iOS Android | expo-av | Reanimated | ปานกลาง |
| Flutter | iOS Android | audio_streamer | CustomPainter | ปานกลาง |
| Swift (iOS Native) | iOS | AVAudioEngine | SpriteKit | สูง |
| Kotlin (Android) | Android | AudioRecord | Canvas | สูง |
Blow Detection
# === Web Audio Blow Detection ===
# JavaScript Implementation
# async function initBlowDetection(onBlow) {
# const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
# const audioContext = new AudioContext();
# const source = audioContext.createMediaStreamSource(stream);
# const analyser = audioContext.createAnalyser();
#
# analyser.fftSize = 2048;
# analyser.smoothingTimeConstant = 0.8;
# source.connect(analyser);
#
# const bufferLength = analyser.frequencyBinCount;
# const dataArray = new Uint8Array(bufferLength);
#
# const BLOW_THRESHOLD = 150; // 0-255
# const LOW_FREQ_START = 5; // ~200Hz
# const LOW_FREQ_END = 15; // ~500Hz
# let isBlowing = false;
# let blowTimeout = null;
#
# function detectBlow() {
# analyser.getByteFrequencyData(dataArray);
#
# // Focus on low frequencies (blow sound range)
# let sum = 0;
# for (let i = LOW_FREQ_START; i < LOW_FREQ_END; i++) {
# sum += dataArray[i];
# }
# const average = sum / (LOW_FREQ_END - LOW_FREQ_START);
#
# if (average > BLOW_THRESHOLD && !isBlowing) {
# isBlowing = true;
# onBlow();
# blowTimeout = setTimeout(() => { isBlowing = false; }, 1000);
# }
#
# requestAnimationFrame(detectBlow);
# }
#
# detectBlow();
# }
#
# // Usage
# initBlowDetection(() => {
# console.log('Blow detected! Extinguish candles!');
# extinguishCandles();
# });
from dataclasses import dataclass
@dataclass
class AudioConfig:
param: str
value: str
purpose: str
configs = [
AudioConfig("fftSize", "2048", "Frequency resolution — higher = more precise"),
AudioConfig("smoothingTimeConstant", "0.8", "Smooth frequency data — reduce noise"),
AudioConfig("BLOW_THRESHOLD", "150 (0-255)", "Volume level to trigger blow detection"),
AudioConfig("LOW_FREQ_START", "5 (~200Hz)", "Start of blow frequency range"),
AudioConfig("LOW_FREQ_END", "15 (~500Hz)", "End of blow frequency range"),
AudioConfig("Debounce", "1000ms", "Prevent multiple triggers from single blow"),
]
print("=== Audio Configuration ===")
for c in configs:
print(f" [{c.param}] Value: {c.value}")
print(f" Purpose: {c.purpose}")
Candle Animation
# === CSS Candle Animation ===
# /* Candle Base */
# .candle {
# width: 30px;
# height: 120px;
# background: linear-gradient(to bottom, #fff3b0, #ffd700);
# border-radius: 3px 3px 0 0;
# position: relative;
# margin: 0 15px;
# }
#
# /* Flame */
# .flame {
# width: 20px;
# height: 40px;
# background: radial-gradient(ellipse at bottom,
# #ff6600 0%, #ffcc00 40%, transparent 70%);
# border-radius: 50% 50% 30% 30%;
# position: absolute;
# top: -45px;
# left: 50%;
# transform: translateX(-50%);
# animation: flicker 0.3s infinite alternate;
# transition: opacity 0.5s, transform 0.5s;
# }
#
# @keyframes flicker {
# 0% { transform: translateX(-50%) scale(1) rotate(-2deg); }
# 50% { transform: translateX(-50%) scale(1.05) rotate(1deg); }
# 100% { transform: translateX(-50%) scale(0.95) rotate(-1deg); }
# }
#
# /* Extinguished */
# .flame.out {
# opacity: 0;
# transform: translateX(-50%) scale(0);
# }
#
# /* Smoke */
# .smoke {
# width: 4px;
# height: 30px;
# background: rgba(150, 150, 150, 0.3);
# position: absolute;
# top: -50px;
# left: 50%;
# transform: translateX(-50%);
# opacity: 0;
# animation: smoke-rise 2s ease-out forwards;
# }
#
# @keyframes smoke-rise {
# 0% { opacity: 0.8; transform: translateX(-50%) translateY(0); }
# 100% { opacity: 0; transform: translateX(-50%) translateY(-40px); }
# }
# Confetti Animation (JavaScript)
# function createConfetti(count = 100) {
# for (let i = 0; i < count; i++) {
# const confetti = document.createElement('div');
# confetti.className = 'confetti';
# confetti.style.left = Math.random() * 100 + 'vw';
# confetti.style.background = `hsl(, 80%, 60%)`;
# confetti.style.animationDuration = Math.random() * 2 + 1 + 's';
# confetti.style.animationDelay = Math.random() * 0.5 + 's';
# document.body.appendChild(confetti);
# setTimeout(() => confetti.remove(), 3000);
# }
# }
@dataclass
class AnimationLayer:
element: str
technique: str
trigger: str
duration: str
layers = [
AnimationLayer("Flame flicker", "CSS @keyframes + transform", "Always (while lit)", "0.3s infinite"),
AnimationLayer("Flame glow", "CSS radial-gradient + box-shadow", "Always (while lit)", "0.5s infinite"),
AnimationLayer("Extinguish", "CSS transition opacity + scale", "On blow detect", "0.5s ease-out"),
AnimationLayer("Smoke trail", "CSS @keyframes translateY", "After extinguish", "2s ease-out"),
AnimationLayer("Confetti", "JS DOM + CSS @keyframes", "All candles out", "1-3s random"),
AnimationLayer("Happy Birthday", "Audio play()", "All candles out", "30s"),
]
print("\n=== Animation Layers ===")
for a in layers:
print(f" [{a.element}] Tech: {a.technique}")
print(f" Trigger: {a.trigger} | Duration: {a.duration}")
Mobile Development
# === React Native / Flutter Implementation ===
# React Native (Expo) — Blow Detection
# import { Audio } from 'expo-av';
#
# const startBlowDetection = async () => {
# const { status } = await Audio.requestPermissionsAsync();
# if (status !== 'granted') return;
#
# const recording = new Audio.Recording();
# await recording.prepareToRecordAsync({
# android: { ...Audio.RecordingOptionsPresets.HIGH_QUALITY.android },
# ios: { ...Audio.RecordingOptionsPresets.HIGH_QUALITY.ios },
# });
#
# recording.setOnRecordingStatusUpdate((status) => {
# if (status.metering && status.metering > -20) {
# // Blow detected (metering is in dB, -160 to 0)
# handleBlow();
# }
# });
#
# await recording.startAsync();
# };
# Flutter — Blow Detection
# import 'package:audio_streamer/audio_streamer.dart';
#
# class BlowDetector {
# final AudioStreamer _streamer = AudioStreamer();
# final double _threshold = 0.5;
#
# void start(VoidCallback onBlow) {
# _streamer.start((List samples) {
# double rms = _calculateRMS(samples);
# if (rms > _threshold) {
# onBlow();
# }
# });
# }
#
# double _calculateRMS(List samples) {
# double sum = 0;
# for (var s in samples) { sum += s * s; }
# return sqrt(sum / samples.length);
# }
# }
@dataclass
class AppFeature:
feature: str
web: str
react_native: str
flutter: str
features = [
AppFeature("Mic Access", "getUserMedia()", "expo-av Audio", "audio_streamer"),
AppFeature("Blow Detect", "AnalyserNode FFT", "metering dB", "RMS calculation"),
AppFeature("Animation", "CSS + Canvas", "Reanimated + Lottie", "CustomPainter"),
AppFeature("Haptic", "navigator.vibrate()", "expo-haptics", "HapticFeedback"),
AppFeature("Sound", "Audio() play()", "expo-av Sound", "audioplayers"),
AppFeature("Share", "Web Share API", "expo-sharing", "share_plus"),
]
print("Cross-platform Comparison:")
for f in features:
print(f" [{f.feature}]")
print(f" Web: {f.web} | RN: {f.react_native} | Flutter: {f.flutter}")
เคล็ดลับ
- Frequency: กรอง Low Frequency 200-500Hz สำหรับลมเป่า
- Debounce: ตั้ง Debounce 1 วินาทีป้องกัน Trigger ซ้ำ
- Permission: ขอสิทธิ์ Microphone ก่อนเริ่มเสมอ
- Animation: ใช้ CSS Animation สำหรับ Performance ที่ดี
- UX: เพิ่ม Fallback ปุ่มกดสำหรับกรณี Mic ไม่ทำงาน
App เป่าเทียนวันเกิดคืออะไร
แอป Microphone ตรวจลมเป่า Blow Detection Web Audio API เทียนดับ Animation เปลวไฟ ควัน Confetti Canvas CSS วันเกิด ปาร์ตี้ อวยพร
ใช้เทคโนโลยีอะไร
Web Audio API getUserMedia AnalyserNode Canvas CSS Animation React Native expo-av Flutter audio_streamer Haptic Feedback requestAnimationFrame
ทำ Blow Detection อย่างไร
getUserMedia audio AudioContext AnalyserNode getByteFrequencyData Volume Threshold 150 Low Frequency 200-500Hz Debounce requestAnimationFrame Loop
สร้าง Animation เทียนอย่างไร
CSS @keyframes flicker Gradient ส้มเหลืองแดง Transition opacity scale Smoke rise Confetti DOM Canvas Particle Happy Birthday Audio
สรุป
App เป่าเทียนวันเกิด Blow Detection Microphone Web Audio API AnalyserNode Animation CSS Canvas Confetti React Native Flutter Mobile Production
