Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Store listens when offline or if submission failed #521

Merged
merged 4 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:finamp/services/finamp_settings_helper.dart';
import 'package:finamp/services/finamp_user_helper.dart';
import 'package:finamp/services/offline_listen_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
Expand All @@ -18,37 +19,37 @@ import 'package:logging/logging.dart';
import 'package:uuid/uuid.dart';

import 'generate_material_color.dart';
import 'models/finamp_models.dart';
import 'models/jellyfin_models.dart';
import 'models/locale_adapter.dart';
import 'models/theme_mode_adapter.dart';
import 'screens/language_selection_screen.dart';
import 'services/locale_helper.dart';
import 'services/theme_mode_helper.dart';
import 'setup_logging.dart';
import 'screens/user_selector.dart';
import 'screens/music_screen.dart';
import 'screens/view_selector.dart';
import 'screens/add_download_location_screen.dart';
import 'screens/add_to_playlist_screen.dart';
import 'screens/album_screen.dart';
import 'screens/player_screen.dart';
import 'screens/splash_screen.dart';
import 'screens/artist_screen.dart';
import 'screens/audio_service_settings_screen.dart';
import 'screens/downloads_error_screen.dart';
import 'screens/downloads_screen.dart';
import 'screens/artist_screen.dart';
import 'screens/downloads_settings_screen.dart';
import 'screens/language_selection_screen.dart';
import 'screens/layout_settings_screen.dart';
import 'screens/logs_screen.dart';
import 'screens/music_screen.dart';
import 'screens/player_screen.dart';
import 'screens/settings_screen.dart';
import 'screens/transcoding_settings_screen.dart';
import 'screens/downloads_settings_screen.dart';
import 'screens/add_download_location_screen.dart';
import 'screens/audio_service_settings_screen.dart';
import 'screens/splash_screen.dart';
import 'screens/tabs_settings_screen.dart';
import 'screens/add_to_playlist_screen.dart';
import 'screens/layout_settings_screen.dart';
import 'screens/transcoding_settings_screen.dart';
import 'screens/user_selector.dart';
import 'screens/view_selector.dart';
import 'services/audio_service_helper.dart';
import 'services/jellyfin_api_helper.dart';
import 'services/downloads_helper.dart';
import 'services/download_update_stream.dart';
import 'services/downloads_helper.dart';
import 'services/jellyfin_api_helper.dart';
import 'services/locale_helper.dart';
import 'services/music_player_background_task.dart';
import 'models/jellyfin_models.dart';
import 'models/finamp_models.dart';
import 'services/theme_mode_helper.dart';
import 'setup_logging.dart';

void main() async {
// If the app has failed, this is set to true. If true, we don't attempt to run the main app since the error app has started.
Expand All @@ -60,6 +61,7 @@ void main() async {
_migrateSortOptions();
_setupFinampUserHelper();
_setupJellyfinApiData();
_setupOfflineListenLogHelper();
await _setupDownloader();
await _setupDownloadsHelper();
await _setupAudioServiceHelper();
Expand Down Expand Up @@ -91,6 +93,10 @@ void _setupJellyfinApiData() {
GetIt.instance.registerSingleton(JellyfinApiHelper());
}

void _setupOfflineListenLogHelper() {
GetIt.instance.registerSingleton(OfflineListenLogHelper());
}

Future<void> _setupDownloadsHelper() async {
GetIt.instance.registerSingleton(DownloadsHelper());
final downloadsHelper = GetIt.instance<DownloadsHelper>();
Expand Down Expand Up @@ -169,6 +175,7 @@ Future<void> setupHive() async {
Hive.registerAdapter(DownloadedImageAdapter());
Hive.registerAdapter(ThemeModeAdapter());
Hive.registerAdapter(LocaleAdapter());
Hive.registerAdapter(OfflineListenAdapter());
await Future.wait([
Hive.openBox<DownloadedParent>("DownloadedParents"),
Hive.openBox<DownloadedSong>("DownloadedItems"),
Expand All @@ -180,6 +187,7 @@ Future<void> setupHive() async {
Hive.openBox<String>("DownloadedImageIds"),
Hive.openBox<ThemeMode>("ThemeMode"),
Hive.openBox<Locale?>(LocaleHelper.boxName),
Hive.openBox<OfflineListen>("OfflineListens")
]);

// If the settings box is empty, we add an initial settings value here.
Expand Down Expand Up @@ -485,4 +493,4 @@ class _DummyCallback {
IsolateNameServer.lookupPortByName('downloader_send_port');
send!.send([id, status, progress]);
}
}
}
40 changes: 38 additions & 2 deletions lib/models/finamp_models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:uuid/uuid.dart';
import 'package:path/path.dart' as path_helper;
import 'package:uuid/uuid.dart';

import '../services/finamp_settings_helper.dart';
import 'jellyfin_models.dart';
import '../services/get_internal_song_dir.dart';
import 'jellyfin_models.dart';

part 'finamp_models.g.dart';

Expand Down Expand Up @@ -571,3 +571,39 @@ class DownloadedImage {
downloadLocationId: downloadLocationId,
);
}

@HiveType(typeId: 43)
class OfflineListen {
OfflineListen({
required this.timestamp,
required this.userId,
required this.itemId,
required this.name,
this.artist,
this.album,
this.trackMbid,
});

/// The stop timestamp of the listen, measured in seconds since the epoch.
@HiveField(0)
int timestamp;

@HiveField(1)
String userId;

@HiveField(2)
String itemId;

@HiveField(3)
String name;

@HiveField(4)
String? artist;

@HiveField(5)
String? album;

// The MusicBrainz ID of the track, if available.
@HiveField(6)
String? trackMbid;
}
52 changes: 52 additions & 0 deletions lib/models/finamp_models.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion lib/services/finamp_user_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class FinampUserHelper {
/// Checks if there are any saved users.
bool get isUsersEmpty => _finampUserBox.isEmpty;

/// Loads the id from CurrentUserId. Returns null if no id is stored.
String? get currentUserId => _currentUserIdBox.get("CurrentUserId");

/// Loads the FinampUser with the id from CurrentUserId. Returns null if no
/// user exists.
FinampUser? get currentUser =>
Expand Down Expand Up @@ -61,4 +64,4 @@ class FinampUserHelper {

_finampUserBox.delete(id);
}
}
}
48 changes: 31 additions & 17 deletions lib/services/music_player_background_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:math';
import 'package:android_id/android_id.dart';
import 'package:audio_service/audio_service.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:finamp/services/offline_listen_helper.dart';
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:just_audio/just_audio.dart';
Expand Down Expand Up @@ -97,6 +98,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
);
final _audioServiceBackgroundTaskLogger = Logger("MusicPlayerBackgroundTask");
final _jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();
final _offlineListenLogHelper = GetIt.instance<OfflineListenLogHelper>();
final _finampUserHelper = GetIt.instance<FinampUserHelper>();

/// Set when shuffle mode is changed. If true, [onUpdateQueue] will create a
Expand Down Expand Up @@ -203,6 +205,12 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
if (playbackInfo != null) {
await _jellyfinApiHelper.stopPlaybackProgress(playbackInfo);
}
} else {
final currentIndex = _player.currentIndex;
if (_queueAudioSource.length != 0 && currentIndex != null) {
final item = _getQueueItem(currentIndex);
_offlineListenLogHelper.logOfflineListen(item);
}
}

// Stop playing audio.
Expand Down Expand Up @@ -446,34 +454,40 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
MediaItem? previousItem,
PlaybackState? previousState,
) async {
if (FinampSettingsHelper.finampSettings.isOffline) {
return;
}

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

if (previousItem != null &&
previousState != null &&
// don't submit stop events for idle tracks (at position 0 and not playing)
(previousState.playing ||
previousState.updatePosition != Duration.zero)) {
final playbackData = generatePlaybackProgressInfoFromState(
previousItem,
previousState,
);
if (!isOffline) {
final playbackData = generatePlaybackProgressInfoFromState(
previousItem,
previousState,
);

if (playbackData != null) {
await jellyfinApiHelper.stopPlaybackProgress(playbackData);
if (playbackData != null) {
try {
await _jellyfinApiHelper.stopPlaybackProgress(playbackData);
} catch (e) {
_offlineListenLogHelper.logOfflineListen(previousItem);
}
}
} else {
_offlineListenLogHelper.logOfflineListen(previousItem);
}
}

final playbackData = generatePlaybackProgressInfoFromState(
currentItem,
currentState,
);
if (!isOffline) {
final playbackData = generatePlaybackProgressInfoFromState(
currentItem,
currentState,
);

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

Expand Down
Loading
Loading