Skip to content
Closed
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
44 changes: 36 additions & 8 deletions lib/models/playback/playback_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ final playbackModelHelper = Provider<PlaybackModelHelper>((ref) {
return PlaybackModelHelper(ref: ref);
});

@visibleForTesting
bool useLocalSyncedCopy({required bool isSynced, required bool serverReachable}) => isSynced && !serverReachable;

class PlaybackModelHelper {
const PlaybackModelHelper({required this.ref});

Expand Down Expand Up @@ -267,18 +270,45 @@ class PlaybackModelHelper {

if (firstItemToPlay == null) return null;

final fullItemResponse = await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id);
final syncedItem = await ref.read(syncProvider.notifier).getSyncedItem(firstItemToPlay.id);
final firstItemIsSynced = syncedItem != null && syncedItem.status == TaskStatus.complete;
final isOffline = ref.read(connectivityStatusProvider.select((value) => value == ConnectionState.offline));

if (useLocalSyncedCopy(isSynced: firstItemIsSynced, serverReachable: !isOffline)) {
final offlineModel = await _createOfflinePlaybackModel(
firstItemToPlay,
item.streamModel,
syncedItem,
oldModel: oldModel,
queueSource: effectiveQueueSource,
);
if (offlineModel != null) return offlineModel;
}

Response<ItemBaseModel>? fullItemResponse;
try {
fullItemResponse =
await api.usersUserIdItemsItemIdGet(itemId: firstItemToPlay.id).timeout(const Duration(seconds: 5));
} catch (e) {
log("Error fetching item, falling back to offline: ${e.toString()}");
}

final fullItem = fullItemResponse.body;
final fullItem = fullItemResponse?.body;

if (fullItem == null) {
if (firstItemIsSynced) {
final offlineModel = await _createOfflinePlaybackModel(
firstItemToPlay,
item.streamModel,
syncedItem,
oldModel: oldModel,
queueSource: effectiveQueueSource,
);
if (offlineModel != null) return offlineModel;
}
return null;
}

SyncedItem? syncedItem = await ref.read(syncProvider.notifier).getSyncedItem(fullItem.id);

final firstItemIsSynced = syncedItem != null && syncedItem.status == TaskStatus.complete;

final actualStartPosition = startPosition ?? fullItem.userData.playBackPosition;

final options = {
Expand All @@ -287,8 +317,6 @@ class PlaybackModelHelper {
if (firstItemIsSynced) PlaybackType.offline,
};

final isOffline = ref.read(connectivityStatusProvider.select((value) => value == ConnectionState.offline));

if (firstItemToPlay is AudioModel && firstItemIsSynced) {
final offlinePlayback = await _createOfflinePlaybackModel(
fullItem,
Expand Down
23 changes: 23 additions & 0 deletions test/models/playback/playback_model_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter_test/flutter_test.dart';

import 'package:fladder/models/playback/playback_model.dart';

void main() {
group('useLocalSyncedCopy', () {
test('plays local copy when synced and server is unreachable', () {
expect(useLocalSyncedCopy(isSynced: true, serverReachable: false), isTrue);
});

test('uses server when synced and server is reachable', () {
expect(useLocalSyncedCopy(isSynced: true, serverReachable: true), isFalse);
});

test('does not force local when item is not synced, even if server unreachable', () {
expect(useLocalSyncedCopy(isSynced: false, serverReachable: false), isFalse);
});

test('uses server when not synced and server reachable', () {
expect(useLocalSyncedCopy(isSynced: false, serverReachable: true), isFalse);
});
});
}
Loading