Files
kryz-go-flutter/lib/streaming_audio_business_object.dart
T
robot 0257937859
CI / Lint (pull_request) Successful in 19m19s
CI / Test (pull_request) Failing after 19m19s
CI / Build Android (pull_request) Skipped
CI / Build iOS (pull_request) Skipped
style: apply dart formatting fixes across lib/ and test/
11 files had formatting inconsistencies that caused the
--set-exit-if-changed gate to fail. Automated formatting
via dart format now passes cleanly.
2026-08-01 11:06:17 +00:00

178 lines
5.0 KiB
Dart
Executable File

import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/foundation.dart';
import 'generated/app_config.dart';
import 'kryz_audio_handler.dart';
import 'live_info_business_object.dart';
enum StreamingPlaybackStatus { stopped, buffering, playing }
class StreamingAudioBusinessObject extends ChangeNotifier {
StreamingAudioBusinessObject() {
final initFuture = _audioHandlerInitFuture;
if (initFuture != null) {
initFuture.then((handler) {
if (_isDisposed) {
return;
}
_attachHandler(handler);
});
}
final handler = _audioHandler;
if (handler != null) {
_attachHandler(handler);
}
}
static AudioHandler? _audioHandler;
static Future<AudioHandler>? _audioHandlerInitFuture;
static Future<void> initializeBackgroundAudio() async {
if (_audioHandler != null) {
return;
}
_audioHandlerInitFuture ??=
AudioService.init(
builder: () => KryzAudioHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.kryzgoflutter.audio.channel',
androidNotificationChannelName:
'${AppConfig.appName} Audio Playback',
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
),
)
.then((handler) {
_audioHandler = handler;
return handler;
})
.catchError((error) {
_audioHandlerInitFuture = null;
throw error;
});
await _audioHandlerInitFuture;
}
String get streamUrl => KryzAudioHandler.streamUrl;
StreamSubscription<PlaybackState>? _playbackStateSubscription;
StreamSubscription<MediaItem?>? _mediaItemSubscription;
StreamingPlaybackStatus _playbackStatus = StreamingPlaybackStatus.stopped;
bool _isDisposed = false;
bool _isAttached = false;
String _currentTitle = AppConfig.defaultTitle;
String _currentSubtitle = AppConfig.defaultArtist;
StreamingPlaybackStatus get playbackStatus => _playbackStatus;
bool get isPlaying => _playbackStatus == StreamingPlaybackStatus.playing;
bool get isBuffering => _playbackStatus == StreamingPlaybackStatus.buffering;
bool get isStopped => _playbackStatus == StreamingPlaybackStatus.stopped;
String get currentTitle => _currentTitle;
String get currentSubtitle => _currentSubtitle;
Future<void> ensureInitialized() async {
await initializeBackgroundAudio();
}
StreamingPlaybackStatus _mapPlaybackStateToStatus(PlaybackState state) {
if (state.processingState == AudioProcessingState.completed) {
return StreamingPlaybackStatus.stopped;
}
if (state.playing) {
if (state.processingState == AudioProcessingState.ready) {
return StreamingPlaybackStatus.playing;
}
return StreamingPlaybackStatus.buffering;
}
if (state.processingState == AudioProcessingState.buffering ||
state.processingState == AudioProcessingState.loading) {
return StreamingPlaybackStatus.buffering;
}
return StreamingPlaybackStatus.stopped;
}
void _setPlaybackStatus(StreamingPlaybackStatus status) {
if (_playbackStatus == status) {
return;
}
_playbackStatus = status;
notifyListeners();
}
Future<void> play() async {
_setPlaybackStatus(StreamingPlaybackStatus.buffering);
final handler = await _requireHandler();
await handler.play();
}
Future<void> stop() async {
final handler = await _requireHandler();
await handler.stop();
_setPlaybackStatus(StreamingPlaybackStatus.stopped);
}
Future<void> updateMetadataFromLiveInfo(LiveInfoSnapshot? snapshot) async {
final handler = await _requireHandler();
await handler.updateMetadataFromLiveInfo(snapshot);
}
Future<KryzAudioHandler> _requireHandler() async {
await initializeBackgroundAudio();
final handler = _audioHandler;
if (handler is! KryzAudioHandler) {
throw StateError('KryzAudioHandler is not initialized');
}
_attachHandler(handler);
return handler;
}
void _attachHandler(AudioHandler handler) {
if (_isAttached) {
return;
}
_isAttached = true;
_playbackStateSubscription = handler.playbackState.listen((state) {
_setPlaybackStatus(_mapPlaybackStateToStatus(state));
});
_mediaItemSubscription = handler.mediaItem.listen((item) {
if (item == null) {
return;
}
final title = item.title.trim().isEmpty
? AppConfig.defaultTitle
: item.title;
final subtitle = (item.artist ?? '').trim().isEmpty
? AppConfig.defaultArtist
: item.artist!.trim();
if (title == _currentTitle && subtitle == _currentSubtitle) {
return;
}
_currentTitle = title;
_currentSubtitle = subtitle;
notifyListeners();
});
}
@override
void dispose() {
_isDisposed = true;
_playbackStateSubscription?.cancel();
_mediaItemSubscription?.cancel();
super.dispose();
}
}