微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何将数据发送到从 UI

如何解决如何将数据发送到从 UI

好吧,我被这个问题困住了。我有一个 audioservice (audioplayer.dart) 的代码,它需要一个 queue 才能播放。我正在使用 playlist.dartaudioplayer.dart 中的 ModalRoute 获取队列并保存在全局变量 queue 中。然后,我初始化 AudioPlayerService。现在一切都很好,但是在扩展 AudioPlayerTaskBackgroundAudioTask 类中,当我尝试访问变量(在 onStart 中)时,它出现的是一个空列表。我不知道问题出在哪里,而且我对 BackgroundAudioTask 类不太熟悉。这是它的样子:

import .....

List<MediaItem> queue = [];

class TempScreen extends StatefulWidget {
  @override
  _TempScreenState createState() => _TempScreenState();
}

class _TempScreenState extends State<TempScreen> {
      @override
      Widget build(BuildContext context) {
        queue = ModalRoute.of(context).settings.arguments;
        // Now HERE THE QUEUE IS FINE
        return Container(.....all ui code);
        }

    // I'm using this button to start the service
    audioPlayerButton() {
    AudioService.start(
      backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,androidNotificationChannelName: 'Audio Service Demo',androidNotificationColor: 0xFF2196f3,androidNotificationIcon: 'mipmap/ic_launcher',androidEnableQueue: true,);
    AudioService.updateQueue(queue);
    print('updated queue at the start');
    print('queue Now is $queue');
    AudioService.setRepeatMode(AudioServiceRepeatMode.none);
    AudioService.setShuffleMode(AudioServiceShuffleMode.none);
    AudioService.play();
  }
      }
    

void _audioPlayerTaskEntrypoint() async {
  AudioServiceBackground.run(() => AudioPlayerTask());
}

class AudioPlayerTask extends BackgroundAudioTask {
  AudioPlayer _player = AudioPlayer();
  Seeker _seeker;
  StreamSubscription<PlaybackEvent> _eventSubscription;
  String kUrl = '';
  String key = "38346591";
  String decrypt = "";
  String preferredQuality = '320';

  int get index => _player.currentIndex == null ? 0 : _player.currentIndex;
  MediaItem get mediaItem => index == null ? queue[0] : queue[index];

  // This is just a function i'm using to get song URLs
  fetchSongUrl(songId) async {
    print('starting fetching url');
    String songUrl =
        "https://www.jiosaavn.com/api.PHP?app_version=5.18.3&api_version=4&readable_version=5.18.3&v=79&_format=json&__call=song.getDetails&pids=" +
            songId;
    var res = await get(songUrl,headers: {"Accept": "application/json"});
    var resEdited = (res.body).split("-->");
    var getMain = jsonDecode(resEdited[1]);
    kUrl = await DesPlugin.decrypt(
        key,getMain[songId]["more_info"]["encrypted_media_url"]);
    kUrl = kUrl.replaceAll('96','$preferredQuality');
    print('fetched url');
    return kUrl;
  }

  @override
  Future<void> onStart(Map<String,dynamic> params) async {
    print('inside onStart of audioPlayertask');
    print('queue Now is $queue');
    // Now HERE QUEUE COMES OUT TO BE AN EMPTY LIST
    final session = await AudioSession.instance;
    await session.configure(AudioSessionConfiguration.speech());

    if (queue.length == 0) {
      print('queue is found to be null.........');
    }
    _player.currentIndexStream.listen((index) {
      if (index != null) AudioServiceBackground.setMediaItem(queue[index]);
    });
    // Propagate all events from the audio player to AudioService clients.
    _eventSubscription = _player.playbackEventStream.listen((event) {
      _broadcastState();
    });
    // Special processing for state transitions.
    _player.processingStateStream.listen((state) {
      switch (state) {
        case ProcessingState.completed:
          AudioService.currentMediaItem != queue.last
              ? AudioService.skipToNext()
              : AudioService.stop();
          break;
        case ProcessingState.ready:
          break;
        default:
          break;
      }
    });

    // Load and broadcast the queue
    print('queue is');
    print(queue);
    print('Index is $index');
    print('MediaItem is');
    print(queue[index]);
    try {
      if (queue[index].extras == null) {
        queue[index] = queue[index].copyWith(extras: {
          'URL': await fetchSongUrl(queue[index].id),});
      }

      await AudioServiceBackground.setQueue(queue);
      await _player.setUrl(queue[index].extras['URL']);
      onPlay();
    } catch (e) {
      print("Error: $e");
      onStop();
    }
  }

  @override
  Future<void> onSkipToQueueItem(String mediaId) async {
    // Then default implementations of onSkipToNext and onSkipToPrevIoUs will
    // delegate to this method.
    final newIndex = queue.indexWhere((item) => item.id == mediaId);
    if (newIndex == -1) return;
    _player.pause();
    if (queue[newIndex].extras == null) {
      queue[newIndex] = queue[newIndex].copyWith(extras: {
        'URL': await fetchSongUrl(queue[newIndex].id),});
      await AudioServiceBackground.setQueue(queue);
      // AudioService.updateQueue(queue);
    }
    await _player.setUrl(queue[newIndex].extras['URL']);
    _player.play();
    await AudioServiceBackground.setMediaItem(queue[newIndex]);
  }

  @override
  Future<void> onUpdateQueue(List<MediaItem> queue) {
    AudioServiceBackground.setQueue(queue = queue);
    return super.onUpdateQueue(queue);
  }

  @override
  Future<void> onPlay() => _player.play();

  @override
  Future<void> onPause() => _player.pause();

  @override
  Future<void> onSeekTo(Duration position) => _player.seek(position);

  @override
  Future<void> onFastForward() => _seekRelative(fastForwardInterval);

  @override
  Future<void> onRewind() => _seekRelative(-rewindInterval);

  @override
  Future<void> onSeekForward(bool begin) async => _seekContinuously(begin,1);

  @override
  Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin,-1);

  @override
  Future<void> onStop() async {
    await _player.dispose();
    _eventSubscription.cancel();

    await _broadcastState();
    // Shut down this task
    await super.onStop();
  }

  Future<void> _seekRelative(Duration offset) async {
    var newPosition = _player.position + offset;
    // Make sure we don't jump out of bounds.
    if (newPosition < Duration.zero) newPosition = Duration.zero;
    if (newPosition > mediaItem.duration) newPosition = mediaItem.duration;
    // Perform the jump via a seek.
    await _player.seek(newPosition);
  }

  void _seekContinuously(bool begin,int direction) {
    _seeker?.stop();
    if (begin) {
      _seeker = Seeker(_player,Duration(seconds: 10 * direction),Duration(seconds: 1),mediaItem)
        ..start();
    }
  }

  /// broadcasts the current state to all clients.
  Future<void> _broadcastState() async {
    await AudioServiceBackground.setState(
      controls: [
        MediaControl.skipToPrevIoUs,if (_player.playing) MediaControl.pause else MediaControl.play,MediaControl.stop,MediaControl.skipToNext,],systemActions: [
        MediaAction.seekTo,MediaAction.seekForward,MediaAction.seekBackward,androidCompactActions: [0,1,3],processingState: _getProcessingState(),playing: _player.playing,position: _player.position,bufferedPosition: _player.bufferedPosition,speed: _player.speed,);
  }

  AudioProcessingState _getProcessingState() {
    switch (_player.processingState) {
      case ProcessingState.idle:
        return AudioProcessingState.stopped;
      case ProcessingState.loading:
        return AudioProcessingState.connecting;
      case ProcessingState.buffering:
        return AudioProcessingState.buffering;
      case ProcessingState.ready:
        return AudioProcessingState.ready;
      case ProcessingState.completed:
        return AudioProcessingState.completed;
      default:
        throw Exception("Invalid state: ${_player.processingState}");
    }
  }
}

This 是 AudioService 的完整代码,以防万一。

解决方法

audio_service 在单独的隔离中运行您的 BackgroundAudioTask。在README中是这样写的:

请注意,您的 UI 和后台任务在单独的隔离区中运行,并且不共享内存。他们交流的唯一方式是通过消息传递。您的 Flutter UI 将仅使用 AudioService API 与后台任务进行通信,而您的后台任务将仅使用 AudioServiceBackground API 与 UI 和其他客户端进行交互。

这里的关键是隔离不共享内存。如果在 UI 隔离中设置“全局”变量,则不会在后台隔离中设置它,因为后台隔离有自己单独的内存块。这就是为什么您的全局 queue 变量为 null。它实际上不是同一个变量,因为现在你实际上有两个变量副本:一个在 UI 隔离中已经设置了一个值,另一个在后台隔离中还没有设置一个值.

现在,您的后台隔离会“稍后”将其自己的队列变量副本设置为某些内容,这是通过消息传递 API 实现的,您可以在该 API 中将队列从 UI 隔离传递到 updateQueue 和后台隔离接收该消息并将其存储到 onUpdateQueue 中变量的自己的副本中。如果您在此之后打印出队列,它将不再为空。

在您的 onStart 中还有一行是您尝试设置队列的地方,尽管您可能应该删除该代码并让队列仅在 onUpdateQueue 中设置。您不应尝试访问 onStart 中的队列,因为您的队列直到 onUpdateQueue 才会收到其值。如果您想在设置之前避免任何空指针异常,您可以将后台隔离中的队列初始化为一个空列表,它最终将被 onUpdateQueue 中的非空列表替换而不会为空。

我还建议您避免将 queue 设为全局变量。全局变量通常很糟糕,但在这种情况下,实际上可能会让您误以为队列变量在 UI 和后台隔离中是相同的,而实际上每个隔离都有自己的变量副本,可能具有不同的值。因此,如果您创建两个单独的“本地”变量,您的代码会更清晰。一个在 UI 中,一个在后台任务中。

另外一个建议是,您应该注意消息传递 API 中的方法是异步方法。您应该等待音频服务启动,然后再向其发送消息,例如设置队列。并且您应该等待队列设置,然后再尝试从队列中播放:

await AudioService.start(....);
// Now the service has started,it is safe to send messages.
await AudioService.updateQueue(...);
// Now the queue has been updated,it is safe to play from it.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。