ryanheise / just_audio

Audio Player
1.03k stars 652 forks source link

PlatformException(-1001, The request timed out., {index: 2}, null) #1313

Closed zqfly closed 1 month ago

zqfly commented 1 month ago

Which API doesn't behave as documented, and how does it misbehave? After calling the play method, the remote URL started to load, but after a period of time, it failed to load.

Minimal reproduction project

class MyAudioHandler extends BaseAudioHandler {
  final _player = AudioPlayer();
  final _bgPlayer = AudioPlayer();
  final _playlist = ConcatenatingAudioSource(children: []);
  final _bgPlaylist = ConcatenatingAudioSource(children: []);
  bool needShowBg = false;

  MyAudioHandler() {
    AudioSession.instance.then((audioSession) async {
      // This line configures the app's audio session, indicating to the OS the
      // type of audio we intend to play. Using the "speech" recipe rather than
      // "music" since we are playing a podcast.
      await audioSession.configure(const AudioSessionConfiguration.speech());
      // Listen to audio interruptions and pause or duck as appropriate.
      _handleInterruptions(audioSession);
      // Use another plugin to load audio to play.
      _loadEmptyPlaylist();
      _loadEmptyBgPlaylist();
      _notifyAudioHandlerAboutPlaybackEvents();
      _listenForDurationChanges();
      _listenForCurrentSongIndexChanges();
      _listenForSequenceStateChanges();
    });
  }

  void _handleInterruptions(AudioSession audioSession) {
    // just_audio can handle interruptions for us, but we have disabled that in
    // order to demonstrate manual configuration.
    // bool playInterrupted = false;
    audioSession.becomingNoisyEventStream.listen((_) {
      debugPrint('PAUSE');
      pause();
    });
    _player.playingStream.listen((playing) {
      // playInterrupted = false;
      if (playing) {
        audioSession.setActive(true);
      }
    });
    audioSession.interruptionEventStream.listen((event) {
      debugPrint('interruption begin: ${event.begin}');
      debugPrint('interruption type: ${event.type}');
      if (event.begin) {
        switch (event.type) {
          case AudioInterruptionType.duck:
            if (audioSession.androidAudioAttributes!.usage ==
                AndroidAudioUsage.game) {
              onSetVoiceVolume(_player.volume / 2);
              onSetBgVolume(_bgPlayer.volume / 2);
            }
            // playInterrupted = false;
            break;
          case AudioInterruptionType.pause:
          case AudioInterruptionType.unknown:
            pause();
            // playInterrupted = true;
            break;
        }
      } else {
        switch (event.type) {
          case AudioInterruptionType.duck:
            onSetVoiceVolume(min(1.0, _player.volume * 2));
            onSetBgVolume(min(1.0, _bgPlayer.volume * 2));
            // playInterrupted = false;
            break;
          case AudioInterruptionType.pause:
            // playInterrupted = false;
            break;
          case AudioInterruptionType.unknown:
            // playInterrupted = false;
            break;
        }
      }
    });
    audioSession.devicesChangedEventStream.listen((event) {
      debugPrint('Devices added: ${event.devicesAdded}');
      debugPrint('Devices removed: ${event.devicesRemoved}');
    });
  }

  Future<void> _loadEmptyPlaylist() async {
    try {
      await _player.setAudioSource(_playlist);
    } catch (e) {
      debugPrint("Error: $e");
    }
  }

  Future<void> _loadEmptyBgPlaylist() async {
    try {
      await _bgPlayer.setAudioSource(_bgPlaylist);
    } catch (e) {
      debugPrint("Error: $e");
    }
  }

  void _notifyAudioHandlerAboutPlaybackEvents() {
    _player.playbackEventStream.listen((PlaybackEvent event) {
      debugPrint("HHH PlaybackEvent:${event.toString()}");
      final playing = _player.playing;
      playbackState.add(playbackState.value.copyWith(
        controls: [
          MediaControl.skipToPrevious,
          if (playing) MediaControl.pause else MediaControl.play,
          MediaControl.stop,
          MediaControl.skipToNext,
        ],
        systemActions: const {
          MediaAction.seek,
        },
        androidCompactActionIndices: const [0, 1, 3],
        processingState: const {
          ProcessingState.idle: AudioProcessingState.idle,
          ProcessingState.loading: AudioProcessingState.loading,
          ProcessingState.buffering: AudioProcessingState.buffering,
          ProcessingState.ready: AudioProcessingState.ready,
          ProcessingState.completed: AudioProcessingState.completed,
        }[_player.processingState]!,
        repeatMode: const {
          LoopMode.off: AudioServiceRepeatMode.none,
          LoopMode.one: AudioServiceRepeatMode.one,
          LoopMode.all: AudioServiceRepeatMode.all,
        }[_player.loopMode]!,
        shuffleMode: (_player.shuffleModeEnabled)
            ? AudioServiceShuffleMode.all
            : AudioServiceShuffleMode.none,
        playing: playing,
        updatePosition: _player.position,
        bufferedPosition: _player.bufferedPosition,
        speed: _player.speed,
        queueIndex: event.currentIndex,
      ));
    });
  }

  void _listenForDurationChanges() {
    _player.durationStream.listen((duration) {
      debugPrint("HHH durationStream:$duration");

      var index = _player.currentIndex;
      final newQueue = queue.value;
      if (index == null || newQueue.isEmpty) return;
      if (_player.shuffleModeEnabled) {
        index = _player.shuffleIndices!.indexOf(index);
      }
      final oldMediaItem = newQueue[index];
      final newMediaItem = oldMediaItem.copyWith(duration: duration);
      newQueue[index] = newMediaItem;
      queue.add(newQueue);
      mediaItem.add(newMediaItem);
    });
  }

  /// 监听音频切换
  void _listenForCurrentSongIndexChanges() {
    _player.currentIndexStream.listen((index) {
      final playlist = queue.value;
      if (index == null || playlist.isEmpty) return;
      if (_player.shuffleModeEnabled) {
        index = _player.shuffleIndices!.indexOf(index);
      }
      mediaItem.add(playlist[index]);
      debugPrint("HHH _listenForCurrentSongIndexChanges index:$index");
    });
  }

  void _listenForSequenceStateChanges() {
    _player.sequenceStateStream.listen((SequenceState? sequenceState) {
      if (sequenceState == null) return;
      final sequence = sequenceState.effectiveSequence;
      if (sequence.isEmpty) return;
      final items = sequence.map((source) => source.tag as MediaItem);
      queue.add(items.toList());
    });
  }

  @override
  Future<void> addQueueItems(List<MediaItem> mediaItems) async {
    // manage Just Audio
    for (MediaItem item in mediaItems) {
      AudioSource result = await _createAudioSource(item);
      _playlist.add(result);
    }

    // notify system
    final newQueue = queue.value..addAll(mediaItems);
    queue.add(newQueue);
  }

  Future<void> addBgQueueItems(List<MediaItem> mediaItems) async {
    // manage Just Audio
    for (MediaItem item in mediaItems) {
      AudioSource result = await _createAudioSource(item);
      _bgPlaylist.add(result);
    }
  }

  @override
  Future<void> addQueueItem(MediaItem mediaItem) async {
    // manage Just Audio
    final audioSource = await _createAudioSource(mediaItem);
    _playlist.add(audioSource);

    // notify system
    final newQueue = queue.value..add(mediaItem);
    queue.add(newQueue);
  }

  Future<AudioSource> _createAudioSource(MediaItem mediaItem) async {
    String url = mediaItem.extras!['url'] as String;
    var fileInfo = await MediaCacheManager.instance.getFile(url);
    if (fileInfo != null) {
      return AudioSource.file(fileInfo.file.path, tag: mediaItem);
    } else {
      return AudioSource.uri(Uri.parse(url), tag: mediaItem);
    }
  }

  bool playListIsEmpty() {
    return _playlist.children.isEmpty;
  }

  int getPlayListLength() {
    return _playlist.children.length;
  }

  @override
  Future<void> removeQueueItemAt(int index) async {
    debugPrint("HHH removeQueueItemAt index:$index");
    if (index < 0 || index >= queue.value.length) return;
    if (_player.shuffleModeEnabled) {
      index = _player.shuffleIndices![index];
    }
    // manage Just Audio
    _playlist.removeAt(index);

    // notify system
    final newQueue = queue.value..removeAt(index);
    queue.add(newQueue);
  }

  Future<void> removeAllList() async {
    // manage Just Audio
    _playlist.removeRange(0, _playlist.length);

    // notify system
    final newQueue = queue.value..removeRange(0, queue.value.length);
    queue.add(newQueue);
  }

  Future<void> removeAllBgList() async {
    // manage Just Audio
    _bgPlaylist.removeRange(0, _bgPlaylist.length);
  }

  @override
  Future<void> play() {
    return _player.play();
  }

  Future<void> playBgAudio() {
    return _bgPlayer.play();
  }

  @override
  Future<void> pause() async {
    debugPrint(
        "HHH pause playlist.length:${_playlist.length};queue.length:${queue.value}");
    return _player.pause();
  }

  Future<void> pauseBgAudio() {
    return _bgPlayer.pause();
  }

  Future<void> clearAll() async {
    await pause();
    await removeAllList();
    return await clearAllBg();
  }

  Future<void> clearAllBg() async {
    await pauseBgAudio();
    return await removeAllBgList();
  }

  @override
  Future<void> seek(Duration position) {
    return _player.seek(position);
  }

  @override
  Future<void> skipToQueueItem(int index) async {
    debugPrint("HHH skipToQueueItem index:$index");
    if (index < 0 || index >= queue.value.length) return;
    if (_player.shuffleModeEnabled) {
      index = _player.shuffleIndices![index];
    }
    _player.seek(Duration.zero, index: index);
  }

  Future<void> skipToIndex(int index) async {
    debugPrint("HHH skipToIndex index:$index");
    if (index < 0 || index >= queue.value.length) return;
    _player.seek(Duration.zero, index: index);
  }

  Future<void> skipToBgIndex(int index) async {
    if (index < 0 || index >= _bgPlaylist.length) return;
    _bgPlayer.seek(Duration.zero, index: index);
  }

  @override
  Future<void> skipToNext() {
    debugPrint("HHH skipToNext");
    if (!_player.hasNext) {
      return skipToQueueItem(0);
    } else {
      return _player.seekToNext();
    }
  }

  @override
  Future<void> skipToPrevious() {
    debugPrint("HHH skipToPrevious");
    if (!_player.hasPrevious) {
      return skipToQueueItem(_playlist.length - 1);
    } else {
      return _player.seekToPrevious();
    }
  }

  void onSetVoiceVolume(double value) {
    _player.setVolume(value);
  }

  void onSetBgVolume(double value) {
    _bgPlayer.setVolume(value);
  }

  @override
  Future<void> setRepeatMode(AudioServiceRepeatMode repeatMode) async {
    switch (repeatMode) {
      case AudioServiceRepeatMode.none:
        _player.setLoopMode(LoopMode.off);
        break;
      case AudioServiceRepeatMode.one:
        _player.setLoopMode(LoopMode.one);
        break;
      case AudioServiceRepeatMode.group:
      case AudioServiceRepeatMode.all:
        _player.setLoopMode(LoopMode.all);
        break;
    }
  }

  Future<void> setBgRepeatMode(AudioServiceRepeatMode repeatMode) async {
    switch (repeatMode) {
      case AudioServiceRepeatMode.none:
        _bgPlayer.setLoopMode(LoopMode.off);
        break;
      case AudioServiceRepeatMode.one:
        _bgPlayer.setLoopMode(LoopMode.one);
        break;
      case AudioServiceRepeatMode.group:
      case AudioServiceRepeatMode.all:
        _bgPlayer.setLoopMode(LoopMode.all);
        break;
    }
  }

  @override
  Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) async {
    if (shuffleMode == AudioServiceShuffleMode.none) {
      _player.setShuffleModeEnabled(false);
    } else {
      await _player.shuffle();
      _player.setShuffleModeEnabled(true);
    }
  }

  @override
  Future<void> customAction(String name, [Map<String, dynamic>? extras]) async {
    if (name == 'dispose') {
      await _player.dispose();
      await _bgPlayer.dispose();
      super.stop();
    }
  }

  @override
  Future<void> stop() async {
    await _player.stop();
    return super.stop();
  }
}

To Reproduce (i.e. user steps, not code) Using a remote URL address to play audio files, an error message appears after loading for a period of time: "PlatformException (-1001, The request timed out., {index: 2}, null) ", then switch to the next track to continue loading, but all the results report errors. Is there any way to solve this?

Error messages PlatformException (-1001, The request timed out., {index: 2}, null)

Expected behavior I hope that if this loading fails, I can continue loading next time instead of never being able to load it again.

Desktop (please complete the following information):

Smartphone (please complete the following information):

Flutter SDK version

[✓] Flutter (Channel stable, 3.19.4, on macOS 14.5 23F79 darwin-arm64, locale
    zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.3)
[✓] Connected device (3 available)
[✓] Network resources

Additional context After loading the error message, the next time you switch to this audio, it will be skipped directly. What is the reason for this?

ryanheise commented 1 month ago

Minimal reproduction project The AudioSource was created using AudioSource.uri (Uri. parse (URL), tag: mediaItem);

Do I need to point out that this is NOT a minimal reproduction project?

Since this is your second time ignoring the instructions, I'm closing the issue. Please do not make it a 3rd.

github-actions[bot] commented 3 weeks ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs, or use StackOverflow if you need help with just_audio.