refactors design to flutter driven service core.
This commit is contained in:
@@ -1,48 +1,94 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:audio_session/audio_session.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:just_audio_background/just_audio_background.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
import 'kryz_audio_handler.dart';
|
||||
import 'live_info_business_object.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) {
|
||||
_setPlaybackStatus(_mapPlayerStateToStatus(state));
|
||||
});
|
||||
StreamingAudioBusinessObject() {
|
||||
if (_audioHandler == null && _audioHandlerInitFuture == null) {
|
||||
initializeBackgroundAudio();
|
||||
}
|
||||
|
||||
final initFuture = _audioHandlerInitFuture;
|
||||
if (initFuture != null) {
|
||||
initFuture.then((handler) {
|
||||
if (_isDisposed) {
|
||||
return;
|
||||
}
|
||||
_attachHandler(handler);
|
||||
});
|
||||
}
|
||||
|
||||
final handler = _audioHandler;
|
||||
if (handler != null) {
|
||||
_attachHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
final String streamUrl;
|
||||
final AudioPlayer _player = AudioPlayer();
|
||||
late final StreamSubscription<PlayerState> _playerStateSubscription;
|
||||
bool _isSourceLoaded = false;
|
||||
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: 'KRYZ 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 _isAudioSessionConfigured = false;
|
||||
bool _playRequested = false;
|
||||
bool _isDisposed = false;
|
||||
bool _isAttached = false;
|
||||
String _currentTitle = 'KRYZ Live Stream';
|
||||
String _currentSubtitle = 'KRYZ Radio';
|
||||
|
||||
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;
|
||||
|
||||
StreamingPlaybackStatus _mapPlayerStateToStatus(PlayerState state) {
|
||||
if (state.processingState == ProcessingState.completed) {
|
||||
StreamingPlaybackStatus _mapPlaybackStateToStatus(PlaybackState state) {
|
||||
if (state.processingState == AudioProcessingState.completed) {
|
||||
return StreamingPlaybackStatus.stopped;
|
||||
}
|
||||
|
||||
if (state.playing) {
|
||||
if (state.processingState == ProcessingState.ready) {
|
||||
if (state.processingState == AudioProcessingState.ready) {
|
||||
return StreamingPlaybackStatus.playing;
|
||||
}
|
||||
|
||||
return StreamingPlaybackStatus.buffering;
|
||||
}
|
||||
|
||||
if (_playRequested && state.processingState != ProcessingState.completed) {
|
||||
if (state.processingState == AudioProcessingState.buffering ||
|
||||
state.processingState == AudioProcessingState.loading) {
|
||||
return StreamingPlaybackStatus.buffering;
|
||||
}
|
||||
|
||||
@@ -58,58 +104,66 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _configureAudioSessionIfNeeded() async {
|
||||
if (_isAudioSessionConfigured) {
|
||||
return;
|
||||
}
|
||||
|
||||
final session = await AudioSession.instance;
|
||||
await session.configure(const AudioSessionConfiguration.music());
|
||||
_isAudioSessionConfigured = true;
|
||||
}
|
||||
|
||||
Future<void> play() async {
|
||||
_playRequested = true;
|
||||
await _configureAudioSessionIfNeeded();
|
||||
_setPlaybackStatus(StreamingPlaybackStatus.buffering);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
final session = await AudioSession.instance;
|
||||
await session.setActive(true);
|
||||
await _player.play();
|
||||
} catch (_) {
|
||||
_playRequested = false;
|
||||
_setPlaybackStatus(StreamingPlaybackStatus.stopped);
|
||||
rethrow;
|
||||
}
|
||||
final handler = await _requireHandler();
|
||||
await handler.play();
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
_playRequested = false;
|
||||
_isSourceLoaded = false;
|
||||
final handler = await _requireHandler();
|
||||
await handler.stop();
|
||||
_setPlaybackStatus(StreamingPlaybackStatus.stopped);
|
||||
await _player.stop();
|
||||
}
|
||||
|
||||
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 ? 'KRYZ Live Stream' : item.title;
|
||||
final subtitle =
|
||||
(item.artist ?? '').trim().isEmpty ? 'KRYZ Radio' : item.artist!.trim();
|
||||
|
||||
if (title == _currentTitle && subtitle == _currentSubtitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
_currentTitle = title;
|
||||
_currentSubtitle = subtitle;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_playerStateSubscription.cancel();
|
||||
_player.dispose();
|
||||
_isDisposed = true;
|
||||
_playbackStateSubscription?.cancel();
|
||||
_mediaItemSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user