iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
AI Engineering

從行程到應援:30 天做出追星實用工具箱系列 第 24 篇

Day 24:網路不穩定與噪音環境下的翻譯表現

  • 分享至 

  • xImage
  •  

在真實追星情境中,粉絲聽直播或看演唱會現場時,最常遇到的不是實驗室裡的完美環境,而是

  1. 網路極度不穩定:捷運上、展演會場人多塞車,訊號在 4G/5G/Wi-Fi 間跳轉甚至斷網。
  2. 高背景噪音:演唱會尖叫聲、直播BGM太大聲、麥克風爆音。

今天我們要實作網路斷線緩衝重連與背景降噪預處理!

  1. 網路容錯機制:實作帶有指數退避的重試機制,網路抖動時不跳錯。
  2. 噪音降噪預處理:設定動態 Threshold,過濾尖叫聲與 BGM 白噪音。
  3. 極端情境 UI 降級顯示:當網路完全中斷時,顯示離線暫存 state,恢復時自動補發。

今日任務

1.網路斷線重試與 Exponential Backoff 工具

當 API 請求因為網路不穩定失敗時,不能直接報錯給用戶看,必須在背景自動重試:

在 lib/core/utils/(或 live_translate/data/services/)建立 network_retry_utils.dart

import 'dart:async';
import 'dart:io';

class NetworkRetryUtils {
  /// 自動重試機制(帶有指數退避,最多重試 maxAttempts 次)
  static Future<T?> retryOnNetworkError<T>(
    Future<T> Function() action, {
    int maxAttempts = 3,
    Duration initialDelay = const Duration(milliseconds: 500),
  }) async {
    int attempts = 0;
    Duration delay = initialDelay;

    while (attempts < maxAttempts) {
      try {
        attempts++;
        return await action();
      } catch (e) {
        // 若達到最大重試次數,宣告失敗
        if (attempts >= maxAttempts) {
          print('網路請求重試 $maxAttempts 次後依然失敗: $e');
          return null;
        }

        // 判斷是否為網路相關異常(SocketException, Timeout 等)
        if (e is SocketException || e is TimeoutException) {
          print('網路不穩定,第 $attempts 次重試中... (${delay.inMilliseconds}ms 後)');
          await Future.delayed(delay);
          delay *= 2; // 時間加倍 (500ms -> 1000ms -> 2000ms)
        } else {
          // 非網路問題(如 401 密碼錯),不重試直接拋出
          rethrow;
        }
      }
    }
    return null;
  }
}

2.噪音下的動態門檻過濾

在演唱會或有大聲 BGM 的直播中,普通的靜音過濾器(VAD)會被背景聲打穿。我們提升 SilenceFilterUtils,加入動態噪音門檻:

更新 silence_filter_utils.dart

class SilenceFilterUtils {
  /// 支援高噪音環境的動態 Noise Gate
  static bool isSpeechInNoisyEnv(
    Uint8List pcmBytes, {
    bool isHighNoiseMode = false, // 是否開啟高噪音/演唱會模式
  }) {
    if (pcmBytes.isEmpty) return false;

    // 高噪音模式下,提高門檻至 -28dB (過濾掉背景音樂與遠處尖叫聲)
    // 一般模式下,維持 -40dB
    double thresholdDb = isHighNoiseMode ? -28.0 : -40.0;

    int sampleCount = pcmBytes.length ~/ 2;
    double sumSquares = 0.0;
    final buffer = pcmBytes.buffer.asByteData(pcmBytes.offsetInBytes, pcmBytes.lengthInBytes);

    for (int i = 0; i < pcmBytes.length - 1; i += 2) {
      int sample = buffer.getInt16(i, Endian.little);
      double normalized = sample / 32768.0;
      sumSquares += normalized * normalized;
    }

    double rms = (sampleCount > 0) ? (sumSquares / sampleCount) : 0.0;
    if (rms < 0.00001) return false;

    double db = 20 * (rms > 0 ? rms : 0.00001);
    return db > thresholdDb;
  }
}

3.整合至 TranslationService 與 UI 降級提示

在 translation_service.dart 呼叫 API 時套用 NetworkRetryUtils

Future<String?> translateWithRetry(String rawText) async {
  return NetworkRetryUtils.retryOnNetworkError(() async {
    // 包裹原本的 http.post 翻譯邏輯
    return await translateText(rawText: rawText, targetLanguage: '繁體中文');
  });
}

在 UI(multi_lang_page.dart)上加上「高噪音抗噪模式」開關與網路異常離線狀態提示

bool _isHighNoiseMode = false; // 是否開啟高抗噪模式

Widget _buildNoiseModeToggle() {
  return SwitchListTile(
    title: const Text('高抗噪/演唱會模式', style: TextStyle(fontSize: 14)),
    subtitle: const Text('過濾背景音樂與尖叫聲', style: TextStyle(fontSize: 12, color: Colors.grey)),
    value: _isHighNoiseMode,
    onChanged: (val) {
      setState(() => _isHighNoiseMode = val);
    },
  );
}

上一篇
Day 23: 處理翻譯功能使用時造成硬體上的問題
系列文
從行程到應援:30 天做出追星實用工具箱 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言