memory leak tracking & fixes
This commit is contained in:
parent
c5fde95c73
commit
506190f882
34 changed files with 146 additions and 38 deletions
|
@ -123,7 +123,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
_unregisterWidget(widget);
|
||||
_queryBarFocusNode.dispose();
|
||||
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
|
||||
_isSelectingNotifier.removeListener(_onActivityChanged);
|
||||
_isSelectingNotifier.dispose();
|
||||
_browseToSelectAnimation.dispose();
|
||||
_subscriptions
|
||||
|
|
|
@ -302,7 +302,7 @@ class _CollectionSectionedContentState extends State<_CollectionSectionedContent
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_appBarHeightNotifier.removeListener(_onAppBarHeightChanged);
|
||||
_appBarHeightNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class FavouriteToggler extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _FavouriteTogglerState extends State<FavouriteToggler> {
|
||||
final ValueNotifier<bool> isFavouriteNotifier = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _isFavouriteNotifier = ValueNotifier(false);
|
||||
|
||||
Set<AvesEntry> get entries => widget.entries;
|
||||
|
||||
|
@ -53,14 +53,14 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
|
|||
@override
|
||||
void dispose() {
|
||||
favourites.removeListener(_onChanged);
|
||||
isFavouriteNotifier.dispose();
|
||||
_isFavouriteNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: isFavouriteNotifier,
|
||||
valueListenable: _isFavouriteNotifier,
|
||||
builder: (context, isFavourite, child) {
|
||||
if (widget.isMenuItem) {
|
||||
return isFavourite
|
||||
|
@ -88,7 +88,7 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
|
|||
favouriteSweeperIcon,
|
||||
color: context.select<AvesColorsData, Color>((v) => v.favourite),
|
||||
),
|
||||
toggledNotifier: isFavouriteNotifier,
|
||||
toggledNotifier: _isFavouriteNotifier,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
@ -97,7 +97,7 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
|
|||
}
|
||||
|
||||
void _onChanged() {
|
||||
isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite);
|
||||
_isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ class FavouriteTogglerCaption extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
|
||||
final ValueNotifier<bool> isFavouriteNotifier = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _isFavouriteNotifier = ValueNotifier(false);
|
||||
|
||||
Set<AvesEntry> get entries => widget.entries;
|
||||
|
||||
|
@ -136,14 +136,14 @@ class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
|
|||
@override
|
||||
void dispose() {
|
||||
favourites.removeListener(_onChanged);
|
||||
isFavouriteNotifier.dispose();
|
||||
_isFavouriteNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: isFavouriteNotifier,
|
||||
valueListenable: _isFavouriteNotifier,
|
||||
builder: (context, isFavourite, child) {
|
||||
return CaptionedButtonText(
|
||||
text: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite,
|
||||
|
@ -154,6 +154,6 @@ class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
|
|||
}
|
||||
|
||||
void _onChanged() {
|
||||
isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite);
|
||||
_isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -391,12 +391,15 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
return dateMillis == null || dateMillis == 0;
|
||||
}).toSet();
|
||||
if (undatedItems.isNotEmpty) {
|
||||
if (!await showSkippableConfirmationDialog(
|
||||
final confirmationDialogDelegate = MoveUndatedConfirmationDialogDelegate();
|
||||
final confirmed = await showSkippableConfirmationDialog(
|
||||
context: context,
|
||||
type: ConfirmationDialog.moveUndatedItems,
|
||||
delegate: MoveUndatedConfirmationDialogDelegate(),
|
||||
delegate: confirmationDialogDelegate,
|
||||
confirmationButtonLabel: context.l10n.continueButtonLabel,
|
||||
)) return false;
|
||||
);
|
||||
confirmationDialogDelegate.dispose();
|
||||
if (!confirmed) return false;
|
||||
|
||||
if (settings.setMetadataDateBeforeFileOp) {
|
||||
final entriesToDate = undatedItems.where((entry) => entry.canEditDate).toSet();
|
||||
|
@ -461,6 +464,10 @@ class MoveUndatedConfirmationDialogDelegate extends ConfirmationDialogDelegate {
|
|||
_setMetadataDate.value = settings.setMetadataDateBeforeFileOp;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_setMetadataDate.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Widget> build(BuildContext context) => [
|
||||
Padding(
|
||||
|
|
|
@ -190,6 +190,7 @@ class _AvesFloatingBarState extends State<AvesFloatingBar> with RouteAware {
|
|||
@override
|
||||
void dispose() {
|
||||
AvesApp.pageRouteObserver.unsubscribe(this);
|
||||
_isBlurAllowedNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,12 @@ class _ThumbnailHighlightOverlayState extends State<ThumbnailHighlightOverlay> {
|
|||
|
||||
static const startAngle = pi * -3 / 4;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_highlightedNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final highlightInfo = context.watch<HighlightInfo>();
|
||||
|
|
|
@ -36,7 +36,7 @@ class ThumbnailScroller extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ThumbnailScrollerState extends State<ThumbnailScroller> {
|
||||
final _cancellableNotifier = ValueNotifier(true);
|
||||
final ValueNotifier<bool> _cancellableNotifier = ValueNotifier(true);
|
||||
late ScrollController _scrollController;
|
||||
bool _isAnimating = false, _isScrolling = false;
|
||||
|
||||
|
@ -71,6 +71,7 @@ class _ThumbnailScrollerState extends State<ThumbnailScroller> {
|
|||
@override
|
||||
void dispose() {
|
||||
_unregisterWidget(widget);
|
||||
_cancellableNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
|
|||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_isValidNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -119,7 +119,13 @@ class _SkippableConfirmationDialog extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _SkippableConfirmationDialogState extends State<_SkippableConfirmationDialog> {
|
||||
final ValueNotifier<bool> _skip = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _skipNotifier = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_skipNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -127,10 +133,10 @@ class _SkippableConfirmationDialogState extends State<_SkippableConfirmationDial
|
|||
scrollableContent: [
|
||||
...widget.delegate.build(context),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: _skip,
|
||||
valueListenable: _skipNotifier,
|
||||
builder: (context, flag, child) => SwitchListTile(
|
||||
value: flag,
|
||||
onChanged: (v) => _skip.value = v,
|
||||
onChanged: (v) => _skipNotifier.value = v,
|
||||
title: Text(context.l10n.doNotAskAgain),
|
||||
),
|
||||
),
|
||||
|
@ -139,7 +145,7 @@ class _SkippableConfirmationDialogState extends State<_SkippableConfirmationDial
|
|||
const CancelButton(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (_skip.value) {
|
||||
if (_skipNotifier.value) {
|
||||
_skipConfirmation(widget.type);
|
||||
}
|
||||
Navigator.maybeOf(context)?.pop(true);
|
||||
|
|
|
@ -88,6 +88,8 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
|
|||
void dispose() {
|
||||
_widthController.dispose();
|
||||
_heightController.dispose();
|
||||
_isValidNotifier.dispose();
|
||||
_mimeTypeNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,13 @@ class _DurationDialogState extends State<DurationDialog> {
|
|||
_seconds = ValueNotifier(seconds % secondsInMinute);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_minutes.dispose();
|
||||
_seconds.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MediaQueryDataProvider(
|
||||
|
|
|
@ -60,6 +60,9 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
@override
|
||||
void dispose() {
|
||||
_isValidNotifier.dispose();
|
||||
_shiftHour.dispose();
|
||||
_shiftMinute.dispose();
|
||||
_shiftSign.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
|
|||
void dispose() {
|
||||
_latitudeController.dispose();
|
||||
_longitudeController.dispose();
|
||||
_isValidNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,12 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
|
|||
_validate();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_isValidNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
|
|
@ -37,6 +37,7 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
|
|||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_isValidNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,12 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
|
|||
ChipSetAction.createVault,
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_appBarHeightNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableProvider<ValueNotifier<AppMode>>.value(
|
||||
|
|
|
@ -96,8 +96,10 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
_subscriptions
|
||||
..forEach((sub) => sub.cancel())
|
||||
..clear();
|
||||
_dotLocationNotifier.removeListener(_updateLocationInfo);
|
||||
_mapController.dispose();
|
||||
_isPageAnimatingNotifier.dispose();
|
||||
_dotLocationNotifier.dispose();
|
||||
_infoLocationNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,12 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
|||
_columnMax = columnRange.$2;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_columnCountNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
|
|
@ -111,7 +111,6 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
|
|||
void dispose() {
|
||||
_queryBarFocusNode.dispose();
|
||||
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
|
||||
_isSelectingNotifier.removeListener(_onActivityChanged);
|
||||
_isSelectingNotifier.dispose();
|
||||
_browseToSelectAnimation.dispose();
|
||||
_subscriptions
|
||||
|
|
|
@ -99,6 +99,12 @@ class FilterNavigationPage<T extends CollectionFilter, CSAD extends ChipSetActio
|
|||
class _FilterNavigationPageState<T extends CollectionFilter, CSAD extends ChipSetActionDelegate<T>> extends State<FilterNavigationPage<T, CSAD>> {
|
||||
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_appBarHeightNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SelectionProvider<FilterGridItem<T>>(
|
||||
|
|
|
@ -27,6 +27,12 @@ class _ChipHighlightOverlayState extends State<ChipHighlightOverlay> {
|
|||
|
||||
CollectionFilter get filter => widget.filter;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_highlightedNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final highlightInfo = context.watch<HighlightInfo>();
|
||||
|
|
|
@ -159,12 +159,18 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
|||
_subscriptions
|
||||
..forEach((sub) => sub.cancel())
|
||||
..clear();
|
||||
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
|
||||
_overlayAnimationController.dispose();
|
||||
_overlayVisible.removeListener(_onOverlayVisibleChanged);
|
||||
_mapController.dispose();
|
||||
_isPageAnimatingNotifier.dispose();
|
||||
_selectedIndexNotifier.dispose();
|
||||
regionCollection?.dispose();
|
||||
_regionCollectionNotifier.value?.dispose();
|
||||
_regionCollectionNotifier.dispose();
|
||||
_dotLocationNotifier.dispose();
|
||||
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
|
||||
_dotEntryNotifier.dispose();
|
||||
_overlayOpacityNotifier.dispose();
|
||||
_overlayVisible.dispose();
|
||||
_overlayAnimationController.dispose();
|
||||
|
||||
// provided collection should be a new instance specifically created
|
||||
// for the `MapPage` widget, so it can be safely disposed here
|
||||
widget.collection.dispose();
|
||||
|
|
|
@ -104,7 +104,13 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_draggedQuickAction.dispose();
|
||||
_draggedAvailableAction.dispose();
|
||||
_quickActionHighlight.dispose();
|
||||
_availableActionHighlight.dispose();
|
||||
_quickActionsChangeNotifier.dispose();
|
||||
_availableActionPageController.dispose();
|
||||
|
||||
_stopLeavingTimer();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
@ -108,6 +108,12 @@ class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMix
|
|||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_isPageAnimatingNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final useTvLayout = settings.useTvLayout;
|
||||
|
|
|
@ -65,7 +65,7 @@ class ViewerController {
|
|||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
_autopilotNotifier.removeListener(_onAutopilotChanged);
|
||||
_autopilotNotifier.dispose();
|
||||
_clearAutopilotAnimations();
|
||||
_stopPlayTimer();
|
||||
_streamController.close();
|
||||
|
|
|
@ -61,6 +61,12 @@ class _XmpCardState extends State<XmpCard> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_indexNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _isIndexed = isIndexed;
|
||||
|
|
|
@ -50,7 +50,8 @@ class _PanoramaPageState extends State<PanoramaPage> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_overlayVisible.removeListener(_onOverlayVisibleChanged);
|
||||
_overlayVisible.dispose();
|
||||
_sensorControl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,7 @@ class _RasterImageViewState extends State<RasterImageView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_fullImageLoaded.dispose();
|
||||
_unregisterFullImage();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
@ -78,6 +78,7 @@ class _VectorImageViewState extends State<VectorImageView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_fullImageLoaded.dispose();
|
||||
_unregisterFullImage();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
|
@ -119,10 +119,10 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
|
|||
@override
|
||||
void dispose() {
|
||||
cleanEntryControllers(entry);
|
||||
_viewerController.dispose();
|
||||
_videoActionDelegate.dispose();
|
||||
_overlayVisible.dispose();
|
||||
_overlayAnimationController.dispose();
|
||||
_overlayVisible.removeListener(_onOverlayVisibleChanged);
|
||||
_videoActionDelegate.dispose();
|
||||
_viewerController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
@ -183,4 +183,4 @@ class SettingKeys {
|
|||
|
||||
// cf Android `Settings.Global.TRANSITION_ANIMATION_SCALE`
|
||||
static const platformTransitionAnimationScaleKey = 'transition_animation_scale';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,16 +9,17 @@ abstract class AvesVideoControllerFactory {
|
|||
void init();
|
||||
|
||||
AvesVideoController buildController(
|
||||
AvesEntryBase entry, {
|
||||
required PlaybackStateHandler playbackStateHandler,
|
||||
required VideoSettings settings,
|
||||
});
|
||||
AvesEntryBase entry, {
|
||||
required PlaybackStateHandler playbackStateHandler,
|
||||
required VideoSettings settings,
|
||||
});
|
||||
}
|
||||
|
||||
abstract class AvesVideoController {
|
||||
final AvesEntryBase _entry;
|
||||
final PlaybackStateHandler playbackStateHandler;
|
||||
final VideoSettings settings;
|
||||
bool _disposed = false;
|
||||
|
||||
AvesEntryBase get entry => _entry;
|
||||
|
||||
|
@ -41,6 +42,8 @@ abstract class AvesVideoController {
|
|||
|
||||
@mustCallSuper
|
||||
Future<void> dispose() async {
|
||||
assert(!_disposed);
|
||||
_disposed = true;
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
|
|
|
@ -80,12 +80,19 @@ class IjkVideoController extends AvesVideoController {
|
|||
@override
|
||||
Future<void> dispose() async {
|
||||
await super.dispose();
|
||||
|
||||
_initialPlayTimer?.cancel();
|
||||
_stopListening();
|
||||
await _valueStreamController.close();
|
||||
await _timedTextStreamController.close();
|
||||
await _instance.release();
|
||||
|
||||
_completedNotifier.dispose();
|
||||
canCaptureFrameNotifier.dispose();
|
||||
canMuteNotifier.dispose();
|
||||
canSetSpeedNotifier.dispose();
|
||||
canSelectStreamNotifier.dispose();
|
||||
sarNotifier.dispose();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
|
|
|
@ -12,7 +12,7 @@ import 'package:media_kit_video/media_kit_video.dart';
|
|||
class MpvVideoController extends AvesVideoController {
|
||||
late Player _instance;
|
||||
late VideoStatus _status;
|
||||
bool _disposed = false, _firstFrameRendered = false;
|
||||
bool _firstFrameRendered = false;
|
||||
final ValueNotifier<VideoController?> _controllerNotifier = ValueNotifier(null);
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
|
||||
|
@ -63,16 +63,21 @@ class MpvVideoController extends AvesVideoController {
|
|||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
assert(!_disposed);
|
||||
_disposed = true;
|
||||
await super.dispose();
|
||||
|
||||
_stopListening();
|
||||
_stopStreamFetchTimer();
|
||||
await _statusStreamController.close();
|
||||
await _timedTextStreamController.close();
|
||||
await _instance.dispose();
|
||||
_controllerNotifier.dispose();
|
||||
|
||||
_completedNotifier.dispose();
|
||||
canCaptureFrameNotifier.dispose();
|
||||
canMuteNotifier.dispose();
|
||||
canSetSpeedNotifier.dispose();
|
||||
canSelectStreamNotifier.dispose();
|
||||
sarNotifier.dispose();
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
|
|
Loading…
Reference in a new issue