From f907a4f3b676a95d49c68d2fc9da1ecd05888990 Mon Sep 17 00:00:00 2001 From: kfj001 Date: Wed, 29 Apr 2026 17:09:28 -0700 Subject: [PATCH] adds buffering intermediate state Co-authored-by: Copilot --- lib/main.dart | 17 ++++- lib/streaming_audio_business_object.dart | 89 +++++++++++++++++------- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 065987a..7fb1838 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -58,6 +58,17 @@ class _MainPageState extends State { StreamingAudioBusinessObject get _audio => widget.audio; late final WebViewController _webViewController; + String _playbackStatusLabel() { + switch (_audio.playbackStatus) { + case StreamingPlaybackStatus.stopped: + return 'Stopped'; + case StreamingPlaybackStatus.buffering: + return 'Buffering...'; + case StreamingPlaybackStatus.playing: + return 'Playing'; + } + } + @override void initState() { super.initState(); @@ -193,17 +204,17 @@ class _MainPageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( - onPressed: _audio.isPlaying ? null : _play, + onPressed: _audio.isStopped ? _play : null, child: const Text('▶️'), ), const SizedBox(width: 12), ElevatedButton( - onPressed: _audio.isPlaying ? _stop : null, + onPressed: _audio.isStopped ? null : _stop, child: const Text('⏹️'), ), const SizedBox(width: 16), Text( - _audio.isPlaying ? 'Playing' : 'Stopped', + _playbackStatusLabel(), style: TextStyle(color: themeColors.statusText), ), ], diff --git a/lib/streaming_audio_business_object.dart b/lib/streaming_audio_business_object.dart index 833a6e1..5ecf1c0 100644 --- a/lib/streaming_audio_business_object.dart +++ b/lib/streaming_audio_business_object.dart @@ -5,18 +5,14 @@ import 'package:flutter/foundation.dart'; import 'package:just_audio_background/just_audio_background.dart'; import 'package:just_audio/just_audio.dart'; +enum StreamingPlaybackStatus { stopped, buffering, playing } + class StreamingAudioBusinessObject extends ChangeNotifier { StreamingAudioBusinessObject({ this.streamUrl = 'https://kryz.out.airtime.pro/kryz_a', }) { _playerStateSubscription = _player.playerStateStream.listen((state) { - final isCurrentlyPlaying = - state.playing && state.processingState != ProcessingState.completed; - - if (_isPlaying != isCurrentlyPlaying) { - _isPlaying = isCurrentlyPlaying; - notifyListeners(); - } + _setPlaybackStatus(_mapPlayerStateToStatus(state)); }); } @@ -24,10 +20,43 @@ class StreamingAudioBusinessObject extends ChangeNotifier { final AudioPlayer _player = AudioPlayer(); late final StreamSubscription _playerStateSubscription; bool _isSourceLoaded = false; - bool _isPlaying = false; + StreamingPlaybackStatus _playbackStatus = StreamingPlaybackStatus.stopped; bool _isAudioSessionConfigured = false; + bool _playRequested = false; - bool get isPlaying => _isPlaying; + StreamingPlaybackStatus get playbackStatus => _playbackStatus; + bool get isPlaying => _playbackStatus == StreamingPlaybackStatus.playing; + bool get isBuffering => _playbackStatus == StreamingPlaybackStatus.buffering; + bool get isStopped => _playbackStatus == StreamingPlaybackStatus.stopped; + + StreamingPlaybackStatus _mapPlayerStateToStatus(PlayerState state) { + if (state.processingState == ProcessingState.completed) { + return StreamingPlaybackStatus.stopped; + } + + if (state.playing) { + if (state.processingState == ProcessingState.ready) { + return StreamingPlaybackStatus.playing; + } + + return StreamingPlaybackStatus.buffering; + } + + if (_playRequested && state.processingState != ProcessingState.completed) { + return StreamingPlaybackStatus.buffering; + } + + return StreamingPlaybackStatus.stopped; + } + + void _setPlaybackStatus(StreamingPlaybackStatus status) { + if (_playbackStatus == status) { + return; + } + + _playbackStatus = status; + notifyListeners(); + } Future _configureAudioSessionIfNeeded() async { if (_isAudioSessionConfigured) { @@ -40,29 +69,39 @@ class StreamingAudioBusinessObject extends ChangeNotifier { } Future play() async { + _playRequested = true; await _configureAudioSessionIfNeeded(); + _setPlaybackStatus(StreamingPlaybackStatus.buffering); - if (!_isSourceLoaded) { - await _player.setAudioSource( - AudioSource.uri( - Uri.parse(streamUrl), - tag: const MediaItem( - id: 'kryz-live-stream', - title: 'KRYZ Live Stream', - artist: 'KRYZ Radio', - album: 'Live', + try { + if (!_isSourceLoaded) { + await _player.setAudioSource( + AudioSource.uri( + Uri.parse(streamUrl), + tag: const MediaItem( + id: 'kryz-live-stream', + title: 'KRYZ Live Stream', + artist: 'KRYZ Radio', + album: 'Live', + ), ), - ), - ); - _isSourceLoaded = true; - } + ); + _isSourceLoaded = true; + } - final session = await AudioSession.instance; - await session.setActive(true); - await _player.play(); + final session = await AudioSession.instance; + await session.setActive(true); + await _player.play(); + } catch (_) { + _playRequested = false; + _setPlaybackStatus(StreamingPlaybackStatus.stopped); + rethrow; + } } Future stop() async { + _playRequested = false; + _setPlaybackStatus(StreamingPlaybackStatus.stopped); await _player.stop(); }