Flutter: AudioPlayer выдает ошибку “Соединение прервано” после навигации по страницам, последующие попытки завершаются провалом.

Вопрос или проблема

Я разрабатываю приложение на Flutter, которое реализует функцию разговора с использованием записи и воспроизведения аудио. Я сталкиваюсь с проблемой, когда воспроизведение аудио не работает после перехода на другую страницу и возвращения на страницу с этой функцией. Вот поведение, которое я наблюдаю:

Когда я покидаю страницу, я вызываю метод dispose() в своем классе ConversationHandler. Когда я возвращаюсь на страницу и инициирую первый разговор, я получаю следующую ошибку:

🆘 Ошибка воспроизведения аудио: соединение прервано
I/flutter (17214): 🎙️ Переключение в режим прослушивания
I/flutter (17214): 🆘 Ошибка воспроизведения аудио: соединение прервано
I/flutter (17214): 🎙️ Переключение в режим прослушивания
I/flutter (17214): 🆘 Ошибка воспроизведения аудио: соединение прервано
I/flutter (17214): 🎙️ Переключение в режим прослушивания

Несмотря на эту ошибку, первая попытка разговора, похоже, работает. Однако при последующих попытках приложение зависает в состоянии “Прослушивание” и не продвигается дальше.

При последующих попытках приложение, кажется, зависает в состоянии “Прослушивание”. Я наблюдаю следующие журналы:

🎵 Отправка аудиофрагмента размером: 2560
I/flutter (17214): 🎵 Отправка аудиофрагмента размером: 2560
I/flutter (17214): 🎵 Отправка аудиофрагмента размером: 2560
I/flutter (17214): 🔄 Сырой ответ от Deepgram: {"type":"Results","channel_index":[0,1],"duration":3.02,"start":0.0,"is_final":true,"speech_final":false,"channel":{"alternatives":[{"transcript":"","confidence":0.0,"words":[]}]},"metadata":{"request_id":"03448721-acf0-4eac-a1c7-d1fcc67f97ba","model_info":{"name":"general","version":"2024-01-26.8851","arch":"base"},"model_uuid":"1ed36bac-f71c-4f3f-a31f-02fd6525c489"},"from_finalize":false}
I/flutter (17214): 🔄 Расшифрованный ответ: {type: Results, channel_index: [0, 1], duration: 3.02, start: 0.0, is_final: true, speech_final: false, channel: {alternatives: [{transcript: , confidence: 0.0, words: []}]}, metadata: {request_id: 03448721-acf0-4eac-a1c7-d1fcc67f97ba, model_info: {name: general, version: 2024-01-26.8851, arch: base}, model_uuid: 1ed36bac-f71c-4f3f-a31f-02fd6525c489}, from_finalize: false}

Кроме того, я иногда наблюдаю следующее поведение в журналах:

🎵 Отправка аудиофрагмента размером: 2560
I/flutter (17214): 🎵 Отправка аудиофрагмента размером: 2560
I/flutter (17214): 🏁 Соединение Deepgram WebSocket закрыто
I/flutter (17214): 🎵 Отправка аудиофрагмента размером: 2560

Соединение Deepgram WebSocket, похоже, закрывается неожиданно, но приложение продолжает отправлять аудиофрагменты.

Я использую пакет just_audio для воспроизведения аудио. Вот соответствующие части моего класса ConversationHandler:

class ConversationHandler {
  // ... (другие свойства)

  late AudioRecorder _recorder;
  late IOWebSocketChannel _deepgramChannel;
  late IO.Socket _conversationSocket;
  late IO.Socket _audioSocket;
  late AudioPlayer _audioPlayer;

  ConversationState _state = ConversationState.idle;
  String _currentTranscript="";

  late Stream<Uint8List> _audioStream;
  StreamSubscription<Uint8List>? _audioStreamSubscription;

  bool _isInitialized = false;

  // ... (конструктор и другие методы)

  Future<void> startConversation() async {
    if (!_isInitialized) {
      print('❗ ConversationHandler не инициализирован');
      _updateStatus("Упс! Что-то пошло не так. Пожалуйста, попробуйте снова.");
      return;
    }

    if (_state != ConversationState.idle) {
      print('❗ Невозможно начать разговор: не в состоянии ожидания');
      return;
    }
    _updateStatus("Прослушивание...");
    await _switchToListeningMode();
  }

  Future<void> _switchToListeningMode() async {
    print('🎙️ Переключение в режим прослушивания');
    _state = ConversationState.listening;

    try {
      bool hasPermission = await _recorder.hasPermission();
      if (!hasPermission) {
        print('❌ Разрешение на использование микрофона не предоставлено');
        _updateStatus("Необходим доступ к микрофону. Пожалуйста, предоставьте разрешение.");
        return;
      }

      _audioStream = await _recorder.startStream(const RecordConfig(
        encoder: AudioEncoder.pcm16bits,
        numChannels: 1,
        sampleRate: 16000,
      ));
      print('🎙️ Аудиопоток запущен');

      _initializeDeepgram();

      _audioStreamSubscription = _audioStream.listen(
        (Uint8List audioChunk) {
          if (_state == ConversationState.listening) {
            if (audioChunk.isNotEmpty) {
              print('🎵 Отправка аудиофрагмента размером: ${audioChunk.length}');
              _deepgramChannel.sink.add(audioChunk);
              _lastAudioDetected = DateTime.now();
            } else {
              print('❗ Аудиофрагмент пуст');
            }
          }
        },
        onError: (error) {
          print('🆘 Ошибка в аудиопотоке: $error');
          _updateStatus("Упс! Что-то пошло не так. Пожалуйста, попробуйте снова.");
        },
      );
    } catch (e) {
      print('🆘 Ошибка при запуске аудиопотока: $e');
      _updateStatus("Упс! Что-то пошло не так. Пожалуйста, попробуйте снова.");
    }
  }

void _initializeDeepgram() {
  final deepgramUrl="wss://api.deepgram.com/v1/listen?encoding=linear16&sample_rate=16000&channels=1&endpointing=500";

  try {
    _deepgramChannel = IOWebSocketChannel.connect(
      Uri.parse(deepgramUrl),
      headers: {
        'Authorization': 'Token $deepgramApiKey',
      },
    );
    print('🌐 Соединение Deepgram WebSocket установлено');
  } catch (e) {
    print('🆘 Ошибка подключения к Deepgram WebSocket: $e');
    _updateStatus("Упс! Что-то пошло не так. Пожалуйста, попробуйте снова.");
    return;
  }

  _deepgramChannel.stream.listen(
    (message) {
      try {
        final response = jsonDecode(message);
        print('🔄 Сырой ответ от Deepgram: $message');
        print('🔄 Расшифрованный ответ: $response');

        if (response['type'] == 'Results' &&
            response['channel'] != null &&
            response['channel']['alternatives'] != null &&
            response['channel']['alternatives'].isNotEmpty) {
          final isFinal = response['is_final'] ?? false;
          final transcript = response['channel']['alternatives'][0]['transcript'] as String? ?? '';

          if (isFinal && transcript.isNotEmpty) {
            _currentTranscript += ' ' + transcript;
            print('📝 Текущий стенограф: $_currentTranscript');
            if (response['speech_final'] == true) {
              _handlePause();
            }
          }
        }
      } catch (e) {
        print('🆘 Ошибка обработки ответа Deepgram: $e');
        _updateStatus("Упс! Что-то пошло не так. Пожалуйста, попробуйте снова.");
      }
    },
    onError: (error) {
      print('🆘 Ошибка Deepgram WebSocket: $error');
      _updateStatus("Упс! Что-то пошло не так. Пожалуйста, попробуйте снова.");
    },
    onDone: () {
      print('🏁 Соединение Deepgram WebSocket закрыто');
    },
  );
}

  Future<void> _playAudio(Uint8List audioBuffer) async {
    print('Воспроизведение аудио');
    try {
      final tempDir = await getTemporaryDirectory();
      print('временная директория получена');
      final tempFile = File('${tempDir.path}/temp_audio.mp3');
      await tempFile.writeAsBytes(audioBuffer);
      print('временный файл получен');
      await _audioPlayer.setFilePath(tempFile.path);
      await _audioPlayer.play();
      await _audioPlayer.playerStateStream.firstWhere(
              (state) => state.processingState == ProcessingState.completed
      );
      print('🔊 Воспроизведение аудио завершено');
    } catch (e) {
      print('🆘 Ошибка воспроизведения аудио: $e');
      _updateStatus("Упс! Что-то пошло не так. Пожалуйста, попробуйте снова.");
    }
  }

 Future<void> stopConversation() async {
    print('🛑 Остановка разговора');
    _state = ConversationState.idle;
    _audioStreamSubscription?.cancel();
    await _recorder.stop();
    _deepgramChannel.sink.close();
    await _audioPlayer.stop();
    _updateStatus("Разговор завершен.");
  }

  Future<void> dispose() async {
    print('♻️ Освобождение ConversationHandler');
    await stopConversation();
    await _conversationSocket.disconnect();
    await _audioSocket.disconnect();
    await _audioPlayer.dispose();
  }

 
}

Мои вопросы:

Что может вызывать ошибку “Соединение прервано” при попытке воспроизведения аудио после навигации по страницам? Почему приложение, похоже, зависает в состоянии “Прослушивание” при последующих попытках разговора, с пустыми стенографами, полученными от Deepgram? Как я могу правильно повторно инициализировать или сбросить аудиоплеер, рекордер и обработчик разговора при возвращении на страницу, чтобы обеспечить согласованное поведение? Почему соединение Deepgram WebSocket закрывается неожиданно, и как я могу убедиться, что оно остается открытым или правильно повторно инициализировать его, когда оно закрывается?

Буду признателен за любые идеи или предложения. Заранее спасибо за помощь!

Ответ или решение

Проблема, с которой вы столкнулись при разработке приложения на Flutter для работы с аудио, действительно имеет несколько аспектов, которые требуют внимания. В вашем случае ошибки "Connection aborted" и зависание приложения в состоянии "Listening" могут быть связаны с неправильной инициализацией и отключением ресурсов после навигации между страницами. Предлагаю рассмотреть возможные причины и решения ваших проблем.

1. Причины возникновения ошибки "Connection aborted" при воспроизведении аудио

Ошибка "Connection aborted" может возникать по нескольким причинам:

  • Неосвобожденные ресурсы: При вызове метода dispose() ваш класс ConversationHandler должен полностью освобождать все ресурсы, связанные с воспроизведением аудио и соединениями с WebSocket. Убедитесь, что все подписки и потоки корректно отменены, и соединения закрыты.

  • Ошибка инициализации AudioPlayer: Убедитесь, что объект AudioPlayer правильно создаётся и инициализируется перед его использованием. Если у вас есть какие-либо ошибки или исключения во время инициализации, они могут привести к тому, что воспроизведение не будет работать должным образом.

2. Зависание приложения в "Listening" состоянии

Зависание в состоянии "Listening" может происходить из-за того, что код не обрабатывает изменения состояния корректно:

  • Неправильное управление состоянием: Возможно, ваше состояние ConversationState не обновляется должным образом, после возвращения на страницу. Убедитесь, что вы правильно обрабатываете изменения состояния, особенно при взаимодействии с потоками.

  • Проблемы с обработкой ответов Deepgram: Похоже, что Deepgram возвращает пустые транскрипты во время последующих попыток. Это может произойти, если соединение WebSocket было закрыто или потеряно, и вы продолжаете отправлять данные, даже когда не можете эффективно их обработать.

3. Правильная переинициализация или сброс

Для достижения стабильной работы возможностей записи и воспроизведения после навигации можно сделать следующее:

  • Переинициализируйте объекты: Добавьте метод для переинициализации AudioRecorder, AudioPlayer и любых связанных потоков. Это особенно важно после завершения работы с ними и перед тем, как начать новую сессию.
void _initializeHandlers() {
  _recorder = AudioRecorder(); // или как у вас это реализовано
  _audioPlayer = AudioPlayer();
  _deepgramChannel = null; // Обнуляйте для корректной повторной инициализации
}
  • Вызовите метод переинициализации при возвращении на страницу: Используйте метод initState() в виджете, чтобы переинициализировать обработчики сразу после возвращения на страницу.

4. Причины внезапного закрытия соединения WebSocket

WebSocket может закрываться по множеству причин:

  • Проблемы с сетью: Если ваше соединение не стабильно, важно обрабатывать исключения, возникающие при попытке подключения.

  • Переподключение WebSocket: Добавьте логику переподключения при получении события onDone. Это поможет восстановить соединение и продолжить слушать.

_onWebSocketClosed() {
  print('🏁 Deepgram WebSocket connection closed, attempting to reconnect...');
  _initializeDeepgram(); // Переинициализация после потери соединения
}

Заключение

Ваша проблема требует комплексного подхода к управлению состоянием, инициализации и освобождению ресурсов. Следуя предложенным рекомендациям, вы сможете улучшить стабильность вашего приложения. Убедитесь, что все потоки, подписки и соединения правильно управляются, чтобы избежать ситуации, когда приложение зависает или не может корректно обработать события.

Если у вас есть дальнейшие вопросы или требуется помощь в реализации вышеописанных решений, не стесняйтесь обращаться!

Оцените материал
Добавить комментарий

Капча загружается...