-
Notifications
You must be signed in to change notification settings - Fork 607
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
Zero memory loss by syncing missing audio frames to server backward #980
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,22 @@ class _MemoriesPageState extends State<MemoriesPage> with AutomaticKeepAliveClie | |
}, | ||
child: CustomScrollView( | ||
slivers: [ | ||
// TODO: FIXME | ||
SliverToBoxAdapter( | ||
child: memoryProvider.missingWals.isNotEmpty | ||
? Padding( | ||
padding: const EdgeInsets.all(32.0), | ||
child: Center( | ||
child: GestureDetector( | ||
onTap: () { | ||
memoryProvider.syncWals(); | ||
}, | ||
child: Text( | ||
"You have ${memoryProvider.missingWals.map((val) => val.seconds).reduce((a, b) => a + b)}s stereo localy, sync now?"), | ||
), | ||
), | ||
) | ||
: SizedBox.shrink()), | ||
Comment on lines
+49
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code block here is responsible for displaying a prompt to the user when there are missing audio frames that need to be synced. However, it's not handling the case where Also, the calculation of total seconds of missing audio frames is done directly in the UI layer. This logic should be moved to the provider or model layer to keep the UI layer clean and focused on rendering. - child: memoryProvider.missingWals.isNotEmpty
+ child: (memoryProvider.missingWals?.isNotEmpty ?? false)
// Move this logic to the provider or model layer
- "You have ${memoryProvider.missingWals.map((val) => val.seconds).reduce((a, b) => a + b)}s stereo localy, sync now?"
+ "You have ${memoryProvider.totalMissingSeconds}s stereo localy, sync now?" Please consider moving the calculation logic to a method in the class MemoryProvider {
// Other properties and methods...
int get totalMissingSeconds {
return missingWals?.map((val) => val.seconds)?.reduce((a, b) => a + b) ?? 0;
}
} |
||
const SliverToBoxAdapter(child: SizedBox(height: 32)), | ||
const SliverToBoxAdapter(child: SpeechProfileCardWidget()), | ||
const SliverToBoxAdapter(child: UpdateFirmwareCardWidget()), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import 'package:friend_private/services/notifications.dart'; | |
import 'package:friend_private/services/services.dart'; | ||
import 'package:friend_private/services/sockets/sdcard_socket.dart'; | ||
import 'package:friend_private/services/sockets/transcription_connection.dart'; | ||
import 'package:friend_private/services/wals.dart'; | ||
import 'package:friend_private/utils/analytics/mixpanel.dart'; | ||
import 'package:friend_private/utils/enums.dart'; | ||
import 'package:friend_private/utils/logger.dart'; | ||
|
@@ -41,6 +42,8 @@ class CaptureProvider extends ChangeNotifier | |
|
||
ServerMemory? get inProgressMemory => _inProgressMemory; | ||
|
||
IWalService get wal => ServiceManager.instance().wal; | ||
|
||
void updateProviderInstances(MemoryProvider? mp, MessageProvider? p) { | ||
memoryProvider = mp; | ||
messageProvider = p; | ||
|
@@ -146,19 +149,16 @@ class CaptureProvider extends ChangeNotifier | |
|
||
Future streamAudioToWs(String id, BleAudioCodec codec) async { | ||
debugPrint('streamAudioToWs in capture_provider'); | ||
if (_bleBytesStream != null) { | ||
_bleBytesStream?.cancel(); | ||
} | ||
_bleBytesStream = await _getBleAudioBytesListener( | ||
id, | ||
onAudioBytesReceived: (List<int> value) { | ||
if (value.isEmpty) return; | ||
if (_socket?.state == SocketServiceState.connected) { | ||
final trimmedValue = value.sublist(3); | ||
_socket?.send(trimmedValue); | ||
} | ||
}, | ||
); | ||
_bleBytesStream?.cancel(); | ||
_bleBytesStream = await _getBleAudioBytesListener(id, onAudioBytesReceived: (List<int> value) { | ||
if (value.isEmpty) return; | ||
wal.onByteStream(value); | ||
if (_socket?.state == SocketServiceState.connected) { | ||
final trimmedValue = value.sublist(3); | ||
_socket?.send(trimmedValue); | ||
wal.onBytesSync(value); | ||
} | ||
}); | ||
Comment on lines
+152
to
+161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new implementation introduces a Write-Ahead Logging (WAL) service to the audio streaming operation. However, there's a potential issue with the order of operations in this block. The - wal.onByteStream(value);
if (_socket?.state == SocketServiceState.connected) {
+ wal.onByteStream(value);
final trimmedValue = value.sublist(3);
_socket?.send(trimmedValue);
wal.onBytesSync(value);
} This way, the byte stream is only logged when the socket is connected. |
||
setAudioBytesConnected(true); | ||
notifyListeners(); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,10 +6,12 @@ import 'package:friend_private/backend/http/api/memories.dart'; | |
import 'package:friend_private/backend/preferences.dart'; | ||
import 'package:friend_private/backend/schema/memory.dart'; | ||
import 'package:friend_private/backend/schema/structured.dart'; | ||
import 'package:friend_private/services/services.dart'; | ||
import 'package:friend_private/services/wals.dart'; | ||
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
import 'package:friend_private/utils/analytics/mixpanel.dart'; | ||
import 'package:friend_private/utils/features/calendar.dart'; | ||
|
||
class MemoryProvider extends ChangeNotifier { | ||
class MemoryProvider extends ChangeNotifier implements IWalServiceListener { | ||
List<ServerMemory> memories = []; | ||
Map<DateTime, List<ServerMemory>> groupedMemories = {}; | ||
|
||
|
@@ -22,6 +24,20 @@ class MemoryProvider extends ChangeNotifier { | |
|
||
List<ServerMemory> processingMemories = []; | ||
|
||
IWalService get _wal => ServiceManager.instance().wal; | ||
List<Wal> _missingWals = []; | ||
List<Wal> get missingWals => _missingWals; | ||
Comment on lines
+27
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - List<Wal> get missingWals => _missingWals;
+ List<Wal> get missingWals => List.from(_missingWals); |
||
|
||
MemoryProvider() { | ||
_wal.subscribe(this, this); | ||
_preload(); | ||
} | ||
Comment on lines
+31
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the constructor, you are subscribing to the - MemoryProvider() {
- _wal.subscribe(this, this);
- _preload();
- }
+ MemoryProvider() {
+ _preload().then((_) {
+ _wal.subscribe(this, this);
+ });
+ }
Comment on lines
+27
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The constructor of + IWalService get _wal => ServiceManager.instance().wal;
+ List<Wal> _missingWals = [];
+ List<Wal> get missingWals => _missingWals;
+
+ MemoryProvider() {
+ _wal.subscribe(this, this);
+ _preload();
+ }
+
+ _preload() async {
+ try {
+ _missingWals = await _wal.getMissingWals();
+ } catch (e) {
+ // Handle or log the error here
+ }
+ notifyListeners();
+ } |
||
|
||
_preload() async { | ||
_missingWals = await _wal.getMissingWals(); | ||
notifyListeners(); | ||
} | ||
Comment on lines
+27
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The - MemoryProvider() {
- _wal.subscribe(this, this);
- _preload();
- }
+ MemoryProvider() {
+ _wal.subscribe(this, this);
+ init();
+ }
+
+ Future<void> init() async {
+ try {
+ await _preload();
+ } catch (e) {
+ // Handle or log exception
+ }
+ }
|
||
|
||
void addProcessingMemory(ServerMemory memory) { | ||
processingMemories.add(memory); | ||
notifyListeners(); | ||
|
@@ -288,6 +304,26 @@ class MemoryProvider extends ChangeNotifier { | |
@override | ||
void dispose() { | ||
_processingMemoryWatchTimer?.cancel(); | ||
_wal.unsubscribe(this); | ||
super.dispose(); | ||
Comment on lines
+307
to
308
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
@override | ||
void onNewMissingWal(Wal wal) async { | ||
_missingWals = await _wal.getMissingWals(); | ||
notifyListeners(); | ||
} | ||
|
||
@override | ||
void onWalSynced(Wal wal, {ServerMemory? memory}) async { | ||
_missingWals = await _wal.getMissingWals(); | ||
notifyListeners(); | ||
} | ||
Comment on lines
+312
to
+321
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The methods + @override
+ void onNewMissingWal(Wal wal) async {
+ await updateMissingWals();
+ }
+
+ @override
+ void onWalSynced(Wal wal, {ServerMemory? memory}) async {
+ await updateMissingWals();
+ }
+
+ Future<void> updateMissingWals() async {
+ try {
+ _missingWals = await _wal.getMissingWals();
+ } catch (e) {
+ // Handle or log the error here
+ }
+ notifyListeners();
+ } |
||
|
||
@override | ||
void onStatusChanged(WalServiceStatus status) {} | ||
|
||
void syncWals() { | ||
_wal.syncAll(); | ||
} | ||
Comment on lines
+324
to
+328
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,11 +7,13 @@ import 'package:flutter_background_service/flutter_background_service.dart'; | |
import 'package:flutter_sound/flutter_sound.dart'; | ||
import 'package:friend_private/services/devices.dart'; | ||
import 'package:friend_private/services/sockets.dart'; | ||
import 'package:friend_private/services/wals.dart'; | ||
|
||
class ServiceManager { | ||
late IMicRecorderService _mic; | ||
late IDeviceService _device; | ||
late ISocketService _socket; | ||
late IWalService _wal; | ||
|
||
static ServiceManager? _instance; | ||
|
||
|
@@ -22,6 +24,7 @@ class ServiceManager { | |
); | ||
sm._device = DeviceService(); | ||
sm._socket = SocketServicePool(); | ||
sm._wal = WalService(); | ||
|
||
return sm; | ||
} | ||
|
@@ -40,6 +43,8 @@ class ServiceManager { | |
|
||
ISocketService get socket => _socket; | ||
|
||
IWalService get wal => _wal; | ||
|
||
static void init() { | ||
if (_instance != null) { | ||
throw Exception("Service manager is initiated"); | ||
|
@@ -49,9 +54,11 @@ class ServiceManager { | |
|
||
Future<void> start() async { | ||
_device.start(); | ||
_wal.start(); | ||
} | ||
|
||
void deinit() { | ||
void deinit() async { | ||
await _wal.stop(); | ||
Comment on lines
+57
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a potential issue with the - void deinit() async {
+ Future<void> deinit() async {
await _wal.stop();
_mic.stop();
_device.stop();
} |
||
_mic.stop(); | ||
_device.stop(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new methods
set wals
andget wals
are handling JSON encoding/decoding directly. This could lead to potential issues if theWal
object's structure changes in the future, as it would require updates in multiple places. It would be better to encapsulate this logic within theWal
class itself, providing a method to convert a list ofWal
objects to a list of strings and vice versa.In the
Wal
class, you can add these static methods:This way, the responsibility of encoding/decoding
Wal
objects is kept within theWal
class, improving maintainability and reducing the risk of errors.