adds buffering intermediate state

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-29 17:09:28 -07:00
co-authored by Copilot
parent 618acc9f38
commit f907a4f3b6
2 changed files with 78 additions and 28 deletions
+14 -3
View File
@@ -58,6 +58,17 @@ class _MainPageState extends State<MainPage> {
StreamingAudioBusinessObject get _audio => widget.audio; StreamingAudioBusinessObject get _audio => widget.audio;
late final WebViewController _webViewController; 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 @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -193,17 +204,17 @@ class _MainPageState extends State<MainPage> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: _audio.isPlaying ? null : _play, onPressed: _audio.isStopped ? _play : null,
child: const Text('▶️'), child: const Text('▶️'),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
ElevatedButton( ElevatedButton(
onPressed: _audio.isPlaying ? _stop : null, onPressed: _audio.isStopped ? null : _stop,
child: const Text('⏹️'), child: const Text('⏹️'),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
_audio.isPlaying ? 'Playing' : 'Stopped', _playbackStatusLabel(),
style: TextStyle(color: themeColors.statusText), style: TextStyle(color: themeColors.statusText),
), ),
], ],
+48 -9
View File
@@ -5,18 +5,14 @@ import 'package:flutter/foundation.dart';
import 'package:just_audio_background/just_audio_background.dart'; import 'package:just_audio_background/just_audio_background.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
enum StreamingPlaybackStatus { stopped, buffering, playing }
class StreamingAudioBusinessObject extends ChangeNotifier { class StreamingAudioBusinessObject extends ChangeNotifier {
StreamingAudioBusinessObject({ StreamingAudioBusinessObject({
this.streamUrl = 'https://kryz.out.airtime.pro/kryz_a', this.streamUrl = 'https://kryz.out.airtime.pro/kryz_a',
}) { }) {
_playerStateSubscription = _player.playerStateStream.listen((state) { _playerStateSubscription = _player.playerStateStream.listen((state) {
final isCurrentlyPlaying = _setPlaybackStatus(_mapPlayerStateToStatus(state));
state.playing && state.processingState != ProcessingState.completed;
if (_isPlaying != isCurrentlyPlaying) {
_isPlaying = isCurrentlyPlaying;
notifyListeners();
}
}); });
} }
@@ -24,10 +20,43 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
final AudioPlayer _player = AudioPlayer(); final AudioPlayer _player = AudioPlayer();
late final StreamSubscription<PlayerState> _playerStateSubscription; late final StreamSubscription<PlayerState> _playerStateSubscription;
bool _isSourceLoaded = false; bool _isSourceLoaded = false;
bool _isPlaying = false; StreamingPlaybackStatus _playbackStatus = StreamingPlaybackStatus.stopped;
bool _isAudioSessionConfigured = false; 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<void> _configureAudioSessionIfNeeded() async { Future<void> _configureAudioSessionIfNeeded() async {
if (_isAudioSessionConfigured) { if (_isAudioSessionConfigured) {
@@ -40,8 +69,11 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
} }
Future<void> play() async { Future<void> play() async {
_playRequested = true;
await _configureAudioSessionIfNeeded(); await _configureAudioSessionIfNeeded();
_setPlaybackStatus(StreamingPlaybackStatus.buffering);
try {
if (!_isSourceLoaded) { if (!_isSourceLoaded) {
await _player.setAudioSource( await _player.setAudioSource(
AudioSource.uri( AudioSource.uri(
@@ -60,9 +92,16 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
final session = await AudioSession.instance; final session = await AudioSession.instance;
await session.setActive(true); await session.setActive(true);
await _player.play(); await _player.play();
} catch (_) {
_playRequested = false;
_setPlaybackStatus(StreamingPlaybackStatus.stopped);
rethrow;
}
} }
Future<void> stop() async { Future<void> stop() async {
_playRequested = false;
_setPlaybackStatus(StreamingPlaybackStatus.stopped);
await _player.stop(); await _player.stop();
} }