Files
kryz-go-flutter/lib/streaming_audio_business_object.dart
T

171 lines
4.8 KiB
Dart

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();
}
}