Skip to content

Commit

Permalink
♻️ Rewrite playback state reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxr1998 committed Aug 1, 2023
1 parent 493bb7e commit 9dc737f
Showing 1 changed file with 124 additions and 80 deletions.
204 changes: 124 additions & 80 deletions lib/services/music_player_background_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
/// new queue.
int? nextInitialIndex;

/// The item that was previously played. Used for reporting playback status.
MediaItem? _previousItem;

/// Set to true when we're stopping the audio service. Used to avoid playback
/// progress reporting.
bool _isStopping = false;
Expand All @@ -67,7 +64,21 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {

// Propagate all events from the audio player to AudioService clients.
_player.playbackEventStream.listen((event) async {
playbackState.add(_transformEvent(event));
final previousState = playbackState.valueOrNull;
final updatedState = _transformEvent(event);

playbackState.add(updatedState);

// Handle track changes
final previousIndex = previousState?.queueIndex;
final currentIndex = updatedState.queueIndex;
if (previousIndex != currentIndex) {
final previousItem =
previousIndex != null ? _getQueueItem(previousIndex) : null;
final currentItem = _getQueueItem(currentIndex!);

onTrackChanged(currentItem, updatedState, previousItem, previousState);
}

if (playbackState.valueOrNull != null &&
playbackState.valueOrNull?.processingState !=
Expand All @@ -87,41 +98,6 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
}
});

_player.currentIndexStream.listen((event) async {
if (event == null) return;

final currentItem = _getQueueItem(event);
mediaItem.add(currentItem);

if (!FinampSettingsHelper.finampSettings.isOffline) {
final jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();

if (_previousItem != null) {
final playbackData = generatePlaybackProgressInfo(
item: _previousItem,
includeNowPlayingQueue: true,
isStopEvent: true,
);

if (playbackData != null) {
await jellyfinApiHelper.stopPlaybackProgress(playbackData);
}
}

final playbackData = generatePlaybackProgressInfo(
item: currentItem,
includeNowPlayingQueue: true,
);

if (playbackData != null) {
await jellyfinApiHelper.reportPlaybackStart(playbackData);
}

// Set item for next index update
_previousItem = currentItem;
}
});

// PlaybackEvent doesn't include shuffle/loops so we listen for changes here
_player.shuffleModeEnabledStream.listen(
(_) => playbackState.add(_transformEvent(_player.playbackEvent)));
Expand Down Expand Up @@ -155,13 +131,9 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {

_isStopping = true;

// Clear the previous item.
_previousItem = null;

// Tell Jellyfin we're no longer playing audio if we're online
if (!FinampSettingsHelper.finampSettings.isOffline) {
final playbackInfo =
generatePlaybackProgressInfo(includeNowPlayingQueue: false);
final playbackInfo = generateCurrentPlaybackProgressInfo();
if (playbackInfo != null) {
await _jellyfinApiHelper.stopPlaybackProgress(playbackInfo);
}
Expand Down Expand Up @@ -349,7 +321,8 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
break;
default:
return Future.error(
"Unsupported AudioServiceRepeatMode! Recieved ${repeatMode.toString()}, requires all, none, or one.");
"Unsupported AudioServiceRepeatMode! Received ${repeatMode.toString()}, requires all, none, or one.",
);
}
} catch (e) {
_audioServiceBackgroundTaskLogger.severe(e);
Expand All @@ -368,34 +341,55 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
}
}

/// Generates PlaybackProgressInfo from current player info. Returns null if
/// _queue is empty. If an item is not supplied, the current queue index will
/// be used.
PlaybackProgressInfo? generatePlaybackProgressInfo({
MediaItem? item,
required bool includeNowPlayingQueue,
bool isStopEvent = false,
}) {
if (item == null) {
final currentIndex = _player.currentIndex;
if (_queueAudioSource.length == 0 || currentIndex == 0) {
// This function relies on _queue having items,
// so we return null if it's empty or no index is played
// and no custom item was passed to avoid more errors.
return null;
onTrackChanged(
MediaItem currentItem,
PlaybackState currentState,
MediaItem? previousItem,
PlaybackState? previousState,
) async {
mediaItem.add(currentItem);

if (!FinampSettingsHelper.finampSettings.isOffline) {
final jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();

if (previousItem != null && previousState != null) {
final playbackData = generatePlaybackProgressInfoFromState(
previousItem,
previousState,
);

if (playbackData != null) {
await jellyfinApiHelper.stopPlaybackProgress(playbackData);
}
}

final playbackData = generatePlaybackProgressInfoFromState(
currentItem,
currentState,
);

if (playbackData != null) {
await jellyfinApiHelper.reportPlaybackStart(playbackData);
}
item = _getQueueItem(currentIndex!);
}
}

/// Generates PlaybackProgressInfo for the supplied item and player info.
PlaybackProgressInfo? generatePlaybackProgressInfo(
MediaItem item, {
required bool isPaused,
required bool isMuted,
required Duration playerPosition,
required String repeatMode,
required bool includeNowPlayingQueue,
}) {
try {
return PlaybackProgressInfo(
itemId: item.extras!["itemJson"]["Id"],
isPaused: !_player.playing,
isMuted: _player.volume == 0,
positionTicks: isStopEvent
? (item.duration?.inMicroseconds ?? 0) * 10
: _player.position.inMicroseconds * 10,
repeatMode: _jellyfinRepeatMode(_player.loopMode),
isPaused: isPaused,
isMuted: isMuted,
positionTicks: playerPosition.inMicroseconds * 10,
repeatMode: repeatMode,
playMethod: item.extras!["shouldTranscode"] ?? false
? "Transcode"
: "DirectPlay",
Expand All @@ -418,6 +412,45 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
}
}

/// Generates PlaybackProgressInfo from current player info.
/// Returns null if _queue is empty.
/// If an item is not supplied, the current queue index will be used.
PlaybackProgressInfo? generateCurrentPlaybackProgressInfo() {
final currentIndex = _player.currentIndex;
if (_queueAudioSource.length == 0 || currentIndex == null) {
// This function relies on _queue having items,
// so we return null if it's empty or no index is played
// and no custom item was passed to avoid more errors.
return null;
}
final item = _getQueueItem(currentIndex);

return generatePlaybackProgressInfo(
item,
isPaused: !_player.playing,
isMuted: _player.volume == 0,
playerPosition: _player.position,
repeatMode: _jellyfinRepeatModeFromLoopMode(_player.loopMode),
includeNowPlayingQueue: false,
);
}

/// Generates PlaybackProgressInfo for the supplied item and playback state.
PlaybackProgressInfo? generatePlaybackProgressInfoFromState(
MediaItem item,
PlaybackState state,
) {
return generatePlaybackProgressInfo(
item,
isPaused: !state.playing,
// TODO: get volume from state?
isMuted: false,
playerPosition: state.position,
repeatMode: _jellyfinRepeatModeFromRepeatMode(state.repeatMode),
includeNowPlayingQueue: true,
);
}

void setNextInitialIndex(int index) {
nextInitialIndex = index;
}
Expand Down Expand Up @@ -496,8 +529,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
try {
JellyfinApiHelper jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();

final playbackInfo =
generatePlaybackProgressInfo(includeNowPlayingQueue: false);
final playbackInfo = generateCurrentPlaybackProgressInfo();
if (playbackInfo != null) {
await jellyfinApiHelper.updatePlaybackProgress(playbackInfo);
}
Expand Down Expand Up @@ -536,7 +568,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
}
}
} else {
// We have to deserialise this because Dart is stupid and can't handle
// We have to deserialize this because Dart is stupid and can't handle
// sending classes through isolates.
final downloadedSong =
DownloadedSong.fromJson(mediaItem.extras!["downloadedSongJson"]);
Expand Down Expand Up @@ -610,24 +642,36 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
}
}

String _jellyfinRepeatMode(LoopMode loopMode) {
AudioServiceRepeatMode _audioServiceRepeatMode(LoopMode loopMode) {
switch (loopMode) {
case LoopMode.all:
return "RepeatAll";
case LoopMode.one:
return "RepeatOne";
case LoopMode.off:
return "RepeatNone";
return AudioServiceRepeatMode.none;
case LoopMode.one:
return AudioServiceRepeatMode.one;
case LoopMode.all:
return AudioServiceRepeatMode.all;
}
}

AudioServiceRepeatMode _audioServiceRepeatMode(LoopMode loopMode) {
String _jellyfinRepeatModeFromLoopMode(LoopMode loopMode) {
switch (loopMode) {
case LoopMode.off:
return AudioServiceRepeatMode.none;
return "RepeatNone";
case LoopMode.one:
return AudioServiceRepeatMode.one;
return "RepeatOne";
case LoopMode.all:
return AudioServiceRepeatMode.all;
return "RepeatAll";
}
}

String _jellyfinRepeatModeFromRepeatMode(AudioServiceRepeatMode repeatMode) {
switch (repeatMode) {
case AudioServiceRepeatMode.none:
return "RepeatNone";
case AudioServiceRepeatMode.one:
return "RepeatOne";
case AudioServiceRepeatMode.all:
case AudioServiceRepeatMode.group:
return "RepeatAll";
}
}

0 comments on commit 9dc737f

Please sign in to comment.