如何处理 "onCancel" 的 Timer.periodic 或 Stream.periodic

How to handle "onCancel" of Timer.periodic or Stream.periodic

每当用户想要播放一些音频时,首先我要等待 5 秒,然后每秒执行一个动作。每当用户按下播放按钮时,我都会调用 onPlay() 方法。我的第一次尝试是这样的:

Timer? _timer;  // I might need to cancel timer also when the user pauses playback by pause button or something

void onPlay() async {
  int _secondsLeft = 5;
  _timer = await Timer.periodic(const Duration(seconds: 1), (_) {
    if (_secondsLeft == 0) _timer?.cancel();
    _textToSpeech.speak(_secondsLeft.toString());
    --_secondsLeft;
  });
  audio.play(_curSong);
}

此尝试无法正常工作,因为 audio.play() 运行 在计时器初始化后立即未完成。我找到 How to make Timer.periodic cancel itself when a condition is reached? 并使用其中一个答案来改进我的代码,如下所示:

StreamSubscription? _timer;

void onPlay() async {
  int _secondsLeft = 5;
  _timer = await Stream.periodic(const Duration(seconds: 1))
      .takeWhile((_) => _secondsLeft > 0)
      .forEach((_) {
        _textToSpeech.speak(_secondsLeft.toString());
        --_secondsLeft;
      });
  audio.play(_curSong);
}

这样效果更好,但是如果用户在初始阶段切换到不同的歌曲或暂停播放,_timer?.cancel() 只会取消流,因此它之后的所有内容 (audio.play(_curSong)) 仍然运行。但我想不仅要取消流,还要跳过 audio.play() 功能。所以我想 运行 audio.play() 只有在 Timer/Stream 正确完成而没有取消之后。

是否有任何选项可以处理 Stream.periodic 或 Timer.periodic 取消操作(“onCancel”)?或者有更好的方法来处理这个特定任务吗?

快到了。您在 .forEach 上的等待实际上 returns 为空。所以据我所知,没有 StreamSubscription。

可能是您问题的简单解决方案的回调是 listen 方法中的可选 onDone。 Stream被取消时不会被调用

import 'dart:async';

void main() async {
  onPlay();

  await Future.delayed(Duration(seconds: 2));

  // Uncomment me to test it
  // _timer?.cancel();
}

StreamSubscription? _timer;

void onPlay() {
  int _secondsLeft = 5;

  _timer = Stream.periodic(const Duration(seconds: 1))
      .takeWhile((_) => _secondsLeft > 0)
      .listen((event) {
    print(_secondsLeft.toString());
    --_secondsLeft;
  }, onDone: () {
    print("It's show time!");
  });
}

你已经很接近了,你只需要在定时器倒计时时播放歌曲,同时取消定时器。 所以:

Timer? _timer;

void onPlay() async {
  int _secondsLeft = 5;
  _timer = await Timer.periodic(const Duration(seconds: 1), (_) {
    if (_secondsLeft == 0) {
      _timer?.cancel();
      audio.play(_curSong);
    } else {
      _textToSpeech.speak(_secondsLeft.toString());
      --_secondsLeft;
    }
  });
}

(请注意,这并没有说 zero,您原来的位置。而是播放音乐。如果您想让它说零,请将 == 0 更改为 < 0.)

用这种写法,取消定时器会停止播放歌曲。