migrate to just audio

This commit is contained in:
Dr-Blank 2024-05-17 11:04:20 -04:00
parent a1dd0e9d3f
commit 01b3dead49
No known key found for this signature in database
GPG key ID: 7452CC63F210A266
22 changed files with 1062 additions and 340 deletions

View file

@ -17,5 +17,6 @@
"riverpod", "riverpod",
"shelfsdk", "shelfsdk",
"tapable" "tapable"
] ],
"cmake.configureOnOpen": false
} }

View file

@ -1,10 +1,18 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:label="whispering_pages" android:label="whispering_pages"
android:name="${applicationName}" android:name="${applicationName}"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<!-- android:name=".MainActivity" -->
<activity <activity
android:name=".MainActivity" android:name="com.ryanheise.audioservice.AudioServiceActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
@ -16,12 +24,12 @@
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. --> to determine the Window background behind the Flutter UI. -->
<meta-data <meta-data
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
/> />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
@ -29,6 +37,22 @@
<meta-data <meta-data
android:name="flutterEmbedding" android:name="flutterEmbedding"
android:value="2" /> android:value="2" />
<!-- ADD THIS "SERVICE" element -->
<service android:name="com.ryanheise.audioservice.AudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<!-- ADD THIS "RECEIVER" element -->
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application> </application>
<!-- Required to query activities that can process text, see: <!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility?hl=en and https://developer.android.com/training/package-visibility?hl=en and
@ -37,8 +61,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. --> In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.PROCESS_TEXT"/> <action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain" />
</intent> </intent>
</queries> </queries>
</manifest> </manifest>

View file

@ -12,8 +12,8 @@ import 'package:whispering_pages/constants/hero_tag_conventions.dart';
import 'package:whispering_pages/features/player/providers/audiobook_player.dart'; import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
import 'package:whispering_pages/router/models/library_item_extras.dart'; import 'package:whispering_pages/router/models/library_item_extras.dart';
import 'package:whispering_pages/settings/app_settings_provider.dart'; import 'package:whispering_pages/settings/app_settings_provider.dart';
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart'; import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart';
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
import '../../../shared/widgets/expandable_description.dart'; import '../../../shared/widgets/expandable_description.dart';
import 'library_item_sliver_app_bar.dart'; import 'library_item_sliver_app_bar.dart';
@ -219,7 +219,7 @@ class LibraryItemMetadata extends StatelessWidget {
return null; return null;
} }
final codec = book.audioFiles.first.codec.toUpperCase(); final codec = book.audioFiles.first.codec.toUpperCase();
final bitrate = book.audioFiles.first.bitRate; // final bitrate = book.audioFiles.first.bitRate;
return codec; return codec;
} }
} }
@ -277,85 +277,83 @@ class LibraryItemActions extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = ref.read(audiobookPlayerProvider); final player = ref.read(audiobookPlayerProvider);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
child: Container( child: Row(
child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
children: [ // play/resume button the same width as image
// play/resume button the same width as image LayoutBuilder(
LayoutBuilder( builder: (context, constraints) {
return SizedBox(
width: calculateWidth(context, constraints),
// a boxy button with icon and text but little rounded corner
child: ElevatedButton.icon(
onPressed: () async {
// play the book
debugPrint('Pressed play/resume button');
// set the book to the player if not already set
if (player.book != book) {
debugPrint('Setting the book ${book.libraryItemId}');
await player.setSourceAudioBook(book);
ref
.read(audiobookPlayerProvider.notifier)
.notifyListeners();
}
// toggle play/pause
player.togglePlayPause();
},
icon: const Icon(Icons.play_arrow_rounded),
label: const Text('Play/Resume'),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
),
);
},
),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
return SizedBox( return SizedBox(
width: calculateWidth(context, constraints), width: constraints.maxWidth * 0.6,
// a boxy button with icon and text but little rounded corner child: Row(
child: ElevatedButton.icon( mainAxisAlignment: MainAxisAlignment.end,
onPressed: () async { children: [
// play the book // read list button
debugPrint('Pressed play/resume button'); IconButton(
// set the book to the player if not already set onPressed: () {},
if (player.book != book) { icon: const Icon(
debugPrint('Setting the book ${book.libraryItemId}'); Icons.playlist_add_rounded,
await player.setSourceAudioBook(book); ),
ref
.read(audiobookPlayerProvider.notifier)
.notifyListeners();
}
// toggle play/pause
player.togglePlayPause();
},
icon: const Icon(Icons.play_arrow_rounded),
label: const Text('Play/Resume'),
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
), ),
), // share button
IconButton(
onPressed: () {},
icon: const Icon(Icons.share_rounded),
),
// download button
IconButton(
onPressed: () {},
icon: const Icon(
Icons.download_rounded,
),
),
// more button
IconButton(
onPressed: () {},
icon: const Icon(
Icons.more_vert_rounded,
),
),
],
), ),
); );
}, },
), ),
Expanded( ),
child: LayoutBuilder( ],
builder: (context, constraints) {
return SizedBox(
width: constraints.maxWidth * 0.6,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// read list button
IconButton(
onPressed: () {},
icon: const Icon(
Icons.playlist_add_rounded,
),
),
// share button
IconButton(
onPressed: () {},
icon: const Icon(Icons.share_rounded),
),
// download button
IconButton(
onPressed: () {},
icon: const Icon(
Icons.download_rounded,
),
),
// more button
IconButton(
onPressed: () {},
icon: const Icon(
Icons.more_vert_rounded,
),
),
],
),
);
},
),
),
],
),
), ),
); );
} }

View file

@ -3,13 +3,15 @@
/// this is needed as audiobook can be a list of audio files instead of a single file /// this is needed as audiobook can be a list of audio files instead of a single file
library; library;
import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/foundation.dart';
import 'package:just_audio/just_audio.dart';
import 'package:just_audio_background/just_audio_background.dart';
import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:shelfsdk/audiobookshelf_api.dart';
/// will manage the audio player instance /// will manage the audio player instance
class AudiobookPlayer extends AudioPlayer { class AudiobookPlayer extends AudioPlayer {
// constructor which takes in the BookExpanded object // constructor which takes in the BookExpanded object
AudiobookPlayer(this.token, this.baseUrl, {super.playerId}) : super() { AudiobookPlayer(this.token, this.baseUrl) : super() {
// set the source of the player to the first track in the book // set the source of the player to the first track in the book
} }
@ -28,7 +30,7 @@ class AudiobookPlayer extends AudioPlayer {
final Uri baseUrl; final Uri baseUrl;
// the current index of the audio file in the [book] // the current index of the audio file in the [book]
final int _currentIndex = 0; // final int _currentIndex = 0;
// available audio tracks // available audio tracks
int? get availableTracks => _book?.tracks.length; int? get availableTracks => _book?.tracks.length;
@ -46,15 +48,32 @@ class AudiobookPlayer extends AudioPlayer {
// if the book is the same, do nothing // if the book is the same, do nothing
return; return;
} }
// first stop the player // first stop the player and clear the source
await stop(); await stop();
var track = book.tracks[_currentIndex]; await setAudioSource(
var url = '$baseUrl${track.contentUrl}?token=$token'; ConcatenatingAudioSource(
await setSourceUrl( useLazyPreparation: true,
url, children: book.tracks.map((track) {
mimeType: track.mimeType, return AudioSource.uri(
); Uri.parse('$baseUrl${track.contentUrl}?token=$token'),
tag: MediaItem(
// Specify a unique ID for each media item:
id: book.libraryItemId + track.index.toString(),
// Metadata to display in the notification:
album: book.metadata.title,
title: book.metadata.title ?? track.title,
artUri: Uri.parse(
'$baseUrl/api/items/${book.libraryItemId}/cover?token=$token&width=800',
),
),
);
}).toList(),
),
).catchError((error) {
debugPrint('Error: $error');
});
_book = book; _book = book;
} }
@ -64,56 +83,37 @@ class AudiobookPlayer extends AudioPlayer {
if (_book == null) { if (_book == null) {
throw StateError('No book is set'); throw StateError('No book is set');
} }
return switch (state) {
PlayerState.playing => pause(), // ! refactor this
PlayerState.paused || return switch (playerState) {
PlayerState.stopped || PlayerState(playing: var isPlaying) => isPlaying ? pause() : play(),
PlayerState.completed =>
resume(),
// do nothing if the player is disposed
PlayerState.disposed => throw StateError('Player is disposed'),
}; };
} }
/// override resume to set the source if the book is not set
@override
Future<void> resume() async {
if (_book == null) {
throw StateError('No book is set');
}
return super.resume();
}
/// a convenience stream for onPositionEveryXSeconds
Stream<Duration> onPositionEvery(Duration duration) => TimerPositionUpdater(
getPosition: getCurrentPosition,
interval: duration,
).positionStream;
/// need to override getDuration and getCurrentPosition to return according to the book instead of the current track /// need to override getDuration and getCurrentPosition to return according to the book instead of the current track
/// this is because the book can be a list of audio files and the player is only aware of the current track /// this is because the book can be a list of audio files and the player is only aware of the current track
/// so we need to calculate the duration and current position based on the book /// so we need to calculate the duration and current position based on the book
@override // @override
Future<Duration?> getDuration() async { // Future<Duration?> getDuration() async {
if (_book == null) { // if (_book == null) {
return null; // return null;
} // }
return _book!.tracks.fold<Duration>( // return _book!.tracks.fold<Duration>(
Duration.zero, // Duration.zero,
(previousValue, element) => previousValue + element.duration, // (previousValue, element) => previousValue + element.duration,
); // );
} // }
@override // @override
Future<Duration?> getCurrentPosition() async { // Future<Duration?> getCurrentPosition() async {
if (_book == null) { // if (_book == null) {
return null; // return null;
} // }
var currentTrack = _book!.tracks[_currentIndex]; // var currentTrack = _book!.tracks[_currentIndex];
var currentTrackDuration = currentTrack.duration; // var currentTrackDuration = currentTrack.duration;
var currentTrackPosition = await super.getCurrentPosition(); // var currentTrackPosition = await super.getCurrentPosition();
return currentTrackPosition != null // return currentTrackPosition != null
? currentTrackPosition + currentTrackDuration // ? currentTrackPosition + currentTrackDuration
: null; // : null;
} // }
} }

View file

@ -24,12 +24,12 @@ class AudiobookPlayer extends _$AudiobookPlayer {
abp.AudiobookPlayer build() { abp.AudiobookPlayer build() {
final api = ref.watch(authenticatedApiProvider); final api = ref.watch(authenticatedApiProvider);
final player = final player =
abp.AudiobookPlayer(api.token!, api.baseUrl, playerId: playerId); abp.AudiobookPlayer(api.token!, api.baseUrl);
ref.onDispose(player.dispose); ref.onDispose(player.dispose);
// bind notify listeners to the player // bind notify listeners to the player
player.onPlayerStateChanged.listen((_) { player.playerStateStream.listen((_) {
notifyListeners(); notifyListeners();
}); });

View file

@ -0,0 +1,67 @@
// this provider is used to manage the player form state
// it will inform about the percentage of the player expanded
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:miniplayer/miniplayer.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'player_form.g.dart';
const double playerMinHeight = 70;
const miniplayerPercentageDeclaration = 0.2;
extension on Ref {
// We can move the previous logic to a Ref extension.
// This enables reusing the logic between providers
T disposeAndListenChangeNotifier<T extends ChangeNotifier>(T notifier) {
onDispose(notifier.dispose);
notifier.addListener(notifyListeners);
// We return the notifier to ease the usage a bit
return notifier;
}
}
@Riverpod(keepAlive: true)
Raw<ValueNotifier<double>> playerExpandProgressNotifier(
PlayerExpandProgressNotifierRef ref,
) {
final ValueNotifier<double> playerExpandProgress =
ValueNotifier(playerMinHeight);
return ref.disposeAndListenChangeNotifier(playerExpandProgress);
}
// @Riverpod(keepAlive: true)
// Raw<ValueNotifier<double>> dragDownPercentageNotifier(
// DragDownPercentageNotifierRef ref,
// ) {
// final ValueNotifier<double> notifier = ValueNotifier(0);
// return ref.disposeAndListenChangeNotifier(notifier);
// }
// a provider that will listen to the playerExpandProgressNotifier and return the percentage of the player expanded
@Riverpod(keepAlive: true)
double playerHeight(
PlayerHeightRef ref,
) {
final playerExpandProgress = ref.watch(playerExpandProgressNotifierProvider);
// on change of the playerExpandProgress invalidate
playerExpandProgress.addListener(() {
ref.invalidateSelf();
});
// listen to the playerExpandProgressNotifier and return the value
return playerExpandProgress.value;
}
// a final MiniplayerController controller = MiniplayerController();
@Riverpod(keepAlive: true)
MiniplayerController miniplayerController(
MiniplayerControllerRef ref,
) {
return MiniplayerController();
}

View file

@ -0,0 +1,58 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'player_form.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$playerExpandProgressNotifierHash() =>
r'e4817361b9a311b61ca23e51082ed11b0a1120ab';
/// See also [playerExpandProgressNotifier].
@ProviderFor(playerExpandProgressNotifier)
final playerExpandProgressNotifierProvider =
Provider<Raw<ValueNotifier<double>>>.internal(
playerExpandProgressNotifier,
name: r'playerExpandProgressNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$playerExpandProgressNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef PlayerExpandProgressNotifierRef
= ProviderRef<Raw<ValueNotifier<double>>>;
String _$playerHeightHash() => r'26dbcb180d494575488d700bd5bdb58c02c224a9';
/// See also [playerHeight].
@ProviderFor(playerHeight)
final playerHeightProvider = Provider<double>.internal(
playerHeight,
name: r'playerHeightProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$playerHeightHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef PlayerHeightRef = ProviderRef<double>;
String _$miniplayerControllerHash() =>
r'489579a18f4e08793de08a4828172bd924768301';
/// See also [miniplayerController].
@ProviderFor(miniplayerController)
final miniplayerControllerProvider = Provider<MiniplayerController>.internal(
miniplayerController,
name: r'miniplayerControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$miniplayerControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef MiniplayerControllerRef = ProviderRef<MiniplayerController>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View file

@ -1,13 +1,14 @@
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:just_audio/just_audio.dart';
import 'package:miniplayer/miniplayer.dart'; import 'package:miniplayer/miniplayer.dart';
import 'package:whispering_pages/api/image_provider.dart'; import 'package:whispering_pages/api/image_provider.dart';
import 'package:whispering_pages/api/library_item_provider.dart'; import 'package:whispering_pages/api/library_item_provider.dart';
import 'package:whispering_pages/features/player/providers/audiobook_player.dart'; import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart'; import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart';
import 'package:whispering_pages/features/player/providers/player_form.dart';
import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart'; import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart';
import 'package:whispering_pages/theme/theme_from_cover_provider.dart'; import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
@ -26,13 +27,7 @@ double percentageFromValueInRange({required final double min, max, value}) {
return (value - min) / (max - min); return (value - min) / (max - min);
} }
const double playerMinHeight = 70;
const double playerMaxHeight = 500;
const miniplayerPercentageDeclaration = 0.2;
final ValueNotifier<double> playerExpandProgress =
ValueNotifier(playerMinHeight);
final MiniplayerController controller = MiniplayerController();
class AudiobookPlayer extends HookConsumerWidget { class AudiobookPlayer extends HookConsumerWidget {
const AudiobookPlayer({super.key}); const AudiobookPlayer({super.key});
@ -64,39 +59,19 @@ class AudiobookPlayer extends HookConsumerWidget {
); );
// add controller to the player state listener // add controller to the player state listener
player.onPlayerStateChanged.listen((state) { player.playerStateStream.listen((state) {
if (state == PlayerState.playing) { if (state.playing) {
playPauseController.reverse();
} else {
playPauseController.forward(); playPauseController.forward();
} else {
playPauseController.reverse();
} }
}); });
final playPauseButton = IconButton( final playPauseButton = AudiobookPlayerPlayPauseButton(
onPressed: () async { playPauseController: playPauseController,
await player.togglePlayPause();
},
icon: AnimatedIcon(
icon: AnimatedIcons.pause_play,
progress: playPauseController,
size: 50,
),
); );
// player.onPositionChanged.listen((event) {
// currentProgress.value = event.inSeconds.toDouble();
// });
// final progressStream = TimerPositionUpdater( const progressBar = AudiobookTotalProgressBar();
// getPosition: player.getCurrentPosition,
// interval: const Duration(milliseconds: 500),
// ).positionStream;
// // a debug that will print the current position of the player
// progressStream.listen((event) {
// debugPrint('Current position: ${event.inSeconds}');
// });
// the widget that will be displayed when the player is expanded
const progressBar = PlayerProgressBar();
// theme from image // theme from image
final imageTheme = ref.watch( final imageTheme = ref.watch(
@ -105,30 +80,38 @@ class AudiobookPlayer extends HookConsumerWidget {
brightness: Theme.of(context).brightness, brightness: Theme.of(context).brightness,
), ),
); );
return Theme(
// get the theme from imageThemeProvider
// max height of the player is the height of the screen
final playerMaxHeight = MediaQuery.of(context).size.height;
return Theme(
data: ThemeData( data: ThemeData(
colorScheme: imageTheme.valueOrNull ?? Theme.of(context).colorScheme, colorScheme: imageTheme.valueOrNull ?? Theme.of(context).colorScheme,
), ),
child: Miniplayer( child: Miniplayer(
valueNotifier: playerExpandProgress, valueNotifier: ref.watch(playerExpandProgressNotifierProvider),
minHeight: playerMinHeight, minHeight: playerMinHeight,
maxHeight: playerMaxHeight, maxHeight: playerMaxHeight,
controller: controller, controller: ref.watch(miniplayerControllerProvider),
elevation: 4, elevation: 4,
onDismissed: () { onDismissed: () {
player.setSourceAudioBook(null); player.setSourceAudioBook(null);
}, },
curve: Curves.easeOut, curve: Curves.easeOut,
builder: (height, percentage) { builder: (height, percentage) {
// return SafeArea(
// child: Text(
// 'percentage: ${percentage.toStringAsFixed(2)}, height: ${height.toStringAsFixed(2)}',
// ),
// );
final bool isFormMiniplayer = final bool isFormMiniplayer =
percentage < miniplayerPercentageDeclaration; percentage < miniplayerPercentageDeclaration;
final double availWidth = MediaQuery.of(context).size.width; final double availWidth = MediaQuery.of(context).size.width;
final maxImgSize = availWidth * 0.4; final maxImgSize = availWidth * 0.4;
final bookTitle = Text(player.book?.metadata.title ?? ''); final bookTitle = Text(player.book?.metadata.title ?? '');
//Declare additional widgets (eg. SkipButton) and variables //Declare additional widgets (eg. SkipButton) and variables
if (!isFormMiniplayer) { if (!isFormMiniplayer) {
var percentageExpandedPlayer = percentageFromValueInRange( var percentageExpandedPlayer = percentageFromValueInRange(
@ -153,7 +136,7 @@ class AudiobookPlayer extends HookConsumerWidget {
percentage: percentageExpandedPlayer, percentage: percentageExpandedPlayer,
) / ) /
2; 2;
const buttonSkipForward = IconButton( const buttonSkipForward = IconButton(
icon: Icon(Icons.forward_30), icon: Icon(Icons.forward_30),
iconSize: 33, iconSize: 33,
@ -164,12 +147,6 @@ class AudiobookPlayer extends HookConsumerWidget {
iconSize: 33, iconSize: 33,
onPressed: onTap, onPressed: onTap,
); );
const buttonPlayExpanded = IconButton(
icon: Icon(Icons.pause_circle_filled),
iconSize: 50,
onPressed: onTap,
);
return PlayerWhenExpanded( return PlayerWhenExpanded(
imgPaddingLeft: paddingLeft, imgPaddingLeft: paddingLeft,
imgPaddingVertical: paddingVertical, imgPaddingVertical: paddingVertical,
@ -178,12 +155,12 @@ class AudiobookPlayer extends HookConsumerWidget {
percentageExpandedPlayer: percentageExpandedPlayer, percentageExpandedPlayer: percentageExpandedPlayer,
text: bookTitle, text: bookTitle,
buttonSkipBackwards: buttonSkipBackwards, buttonSkipBackwards: buttonSkipBackwards,
buttonPlayExpanded: playPauseButton, playPauseButton: playPauseButton,
buttonSkipForward: buttonSkipForward, buttonSkipForward: buttonSkipForward,
progressIndicator: progressBar, progressIndicator: progressBar,
); );
} }
//Miniplayer //Miniplayer
final percentageMiniplayer = percentageFromValueInRange( final percentageMiniplayer = percentageFromValueInRange(
min: playerMinHeight, min: playerMinHeight,
@ -191,16 +168,14 @@ class AudiobookPlayer extends HookConsumerWidget {
playerMinHeight, playerMinHeight,
value: height, value: height,
); );
final elementOpacity = 1 - 1 * percentageMiniplayer; final elementOpacity = 1 - 1 * percentageMiniplayer;
final progressIndicatorHeight = 4 - 4 * percentageMiniplayer;
return PlayerWhenMinimized( return PlayerWhenMinimized(
maxImgSize: maxImgSize, maxImgSize: maxImgSize,
imgWidget: imgWidget, imgWidget: imgWidget,
elementOpacity: elementOpacity, elementOpacity: elementOpacity,
playPauseButton: playPauseButton, playPauseButton: playPauseButton,
progressIndicatorHeight: progressIndicatorHeight,
progressIndicator: progressBar, progressIndicator: progressBar,
); );
}, },
@ -209,34 +184,102 @@ class AudiobookPlayer extends HookConsumerWidget {
} }
} }
class PlayerProgressBar extends HookConsumerWidget { class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
const PlayerProgressBar({ const AudiobookPlayerPlayPauseButton({
super.key,
required this.playPauseController,
});
final AnimationController playPauseController;
@override
Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider);
return switch (player.processingState) {
ProcessingState.loading ||
ProcessingState.buffering =>
const CircularProgressIndicator(),
ProcessingState.completed => IconButton(
onPressed: () async {
await player.seek(const Duration(seconds: 0));
await player.play();
},
icon: const Icon(Icons.replay),
),
ProcessingState.ready => IconButton(
onPressed: () async {
await player.togglePlayPause();
},
icon: AnimatedIcon(
icon: AnimatedIcons.play_pause,
progress: playPauseController,
size: 50,
),
),
ProcessingState.idle => const SizedBox.shrink(),
};
}
}
/// A progress bar that shows the total progress of the audiobook
///
/// for chapter progress, use [AudiobookChapterProgressBar]
class AudiobookTotalProgressBar extends HookConsumerWidget {
const AudiobookTotalProgressBar({
super.key, super.key,
}); });
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider); final player = ref.watch(audiobookPlayerProvider);
final playerState = useState(player.state); // final playerState = useState(player.processingState);
// add a listener to the player state // add a listener to the player state
player.onPlayerStateChanged.listen((state) { // player.processingStateStream.listen((state) {
playerState.value = state; // playerState.value = state;
}); // });
return StreamBuilder<Duration>( return StreamBuilder(
stream: player.onPositionChanged, stream: player.currentIndexStream,
builder: (context, snapshot) { builder: (context, currentTrackIndex) {
return ProgressBar( return StreamBuilder(
progress: snapshot.data ?? const Duration(seconds: 0), stream: player.positionStream,
total: player.book?.duration ?? const Duration(seconds: 0), builder: (context, progress) {
onSeek: player.seek, // totalProgress is the sum of the duration of all the tracks before the current track + the current track position
thumbRadius: 8, final totalProgress =
// thumbColor: Theme.of(context).colorScheme.secondary, player.book?.tracks.sublist(0, currentTrackIndex.data).fold(
thumbGlowColor: Theme.of(context).colorScheme.secondary, const Duration(seconds: 0),
thumbGlowRadius: playerState.value == PlayerState.playing ? 10 : 0, (previousValue, element) =>
previousValue + element.duration,
) ??
const Duration(seconds: 0) +
(progress.data ?? const Duration(seconds: 0));
return StreamBuilder(
stream: player.bufferedPositionStream,
builder: (context, buffered) {
final totalBuffered =
player.book?.tracks.sublist(0, currentTrackIndex.data).fold(
const Duration(seconds: 0),
(previousValue, element) =>
previousValue + element.duration,
) ??
const Duration(seconds: 0) +
(buffered.data ?? const Duration(seconds: 0));
return ProgressBar(
progress: totalProgress,
total: player.book?.duration ?? const Duration(seconds: 0),
onSeek: player.seek,
thumbRadius: 8,
buffered: totalBuffered,
bufferedBarColor: Theme.of(context).colorScheme.secondary,
);
},
);
},
); );
}, },
); );
} }
} }
// ! TODO remove onTap
void onTap() {} void onTap() {}

View file

@ -10,7 +10,7 @@ class PlayerWhenExpanded extends StatelessWidget {
required this.percentageExpandedPlayer, required this.percentageExpandedPlayer,
required this.text, required this.text,
required this.buttonSkipBackwards, required this.buttonSkipBackwards,
required this.buttonPlayExpanded, required this.playPauseButton,
required this.buttonSkipForward, required this.buttonSkipForward,
required this.progressIndicator, required this.progressIndicator,
}); });
@ -23,7 +23,7 @@ class PlayerWhenExpanded extends StatelessWidget {
final double percentageExpandedPlayer; final double percentageExpandedPlayer;
final Text text; final Text text;
final IconButton buttonSkipBackwards; final IconButton buttonSkipBackwards;
final IconButton buttonPlayExpanded; final Widget playPauseButton;
final IconButton buttonSkipForward; final IconButton buttonSkipForward;
final Widget progressIndicator; final Widget progressIndicator;
@ -62,7 +62,7 @@ class PlayerWhenExpanded extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
buttonSkipBackwards, buttonSkipBackwards,
buttonPlayExpanded, playPauseButton,
buttonSkipForward, buttonSkipForward,
], ],
), ),

View file

@ -1,8 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:miniplayer/miniplayer.dart';
import 'package:whispering_pages/features/player/providers/audiobook_player.dart'; import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
import 'package:whispering_pages/features/player/view/audiobook_player.dart'; import 'package:whispering_pages/features/player/providers/player_form.dart';
class PlayerWhenMinimized extends HookConsumerWidget { class PlayerWhenMinimized extends HookConsumerWidget {
const PlayerWhenMinimized({ const PlayerWhenMinimized({
@ -11,20 +10,19 @@ class PlayerWhenMinimized extends HookConsumerWidget {
required this.imgWidget, required this.imgWidget,
required this.elementOpacity, required this.elementOpacity,
required this.playPauseButton, required this.playPauseButton,
required this.progressIndicatorHeight,
required this.progressIndicator, required this.progressIndicator,
}); });
final double maxImgSize; final double maxImgSize;
final Widget imgWidget; final Widget imgWidget;
final double elementOpacity; final double elementOpacity;
final IconButton playPauseButton; final Widget playPauseButton;
final double progressIndicatorHeight;
final Widget progressIndicator; final Widget progressIndicator;
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider); final player = ref.watch(audiobookPlayerProvider);
final controller = ref.watch(miniplayerControllerProvider);
return Column( return Column(
children: [ children: [
Expanded( Expanded(
@ -67,14 +65,14 @@ class PlayerWhenMinimized extends HookConsumerWidget {
), ),
), ),
), ),
IconButton( // IconButton(
icon: const Icon(Icons.fullscreen), // icon: const Icon(Icons.fullscreen),
onPressed: () { // onPressed: () {
controller.animateToHeight(state: PanelState.MAX); // controller.animateToHeight(state: PanelState.MAX);
}, // },
), // ),
Padding( Padding(
padding: const EdgeInsets.only(right: 3), padding: const EdgeInsets.all(8),
child: Opacity( child: Opacity(
opacity: elementOpacity, opacity: elementOpacity,
child: playPauseButton, child: playPauseButton,
@ -83,13 +81,13 @@ class PlayerWhenMinimized extends HookConsumerWidget {
], ],
), ),
), ),
SizedBox( // SizedBox(
height: progressIndicatorHeight, // height: progressIndicatorHeight,
child: Opacity( // child: Opacity(
opacity: elementOpacity, // opacity: elementOpacity,
child: progressIndicator, // child: progressIndicator,
), // ),
), // ),
], ],
); );
} }

View file

@ -1,5 +1,10 @@
import 'package:audio_session/audio_session.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:just_audio_background/just_audio_background.dart'
show JustAudioBackground;
import 'package:just_audio_media_kit/just_audio_media_kit.dart'
show JustAudioMediaKit;
import 'package:whispering_pages/api/server_provider.dart'; import 'package:whispering_pages/api/server_provider.dart';
import 'package:whispering_pages/db/storage.dart'; import 'package:whispering_pages/db/storage.dart';
import 'package:whispering_pages/router/router.dart'; import 'package:whispering_pages/router/router.dart';
@ -10,9 +15,24 @@ import 'package:whispering_pages/theme/theme.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
// for playing audio on windows, linux
JustAudioMediaKit.ensureInitialized();
// initialize the storage // initialize the storage
await initStorage(); await initStorage();
// for configuring how this app will interact with other audio apps
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.speech());
// for playing audio in the background
await JustAudioBackground.init(
androidNotificationChannelId: 'com.whispering_pages.bg_demo.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
);
// run the app
runApp( runApp(
const ProviderScope( const ProviderScope(
child: MyApp(), child: MyApp(),

View file

@ -1,6 +1,9 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:whispering_pages/features/player/providers/player_form.dart';
import 'package:whispering_pages/features/player/view/audiobook_player.dart'; import 'package:whispering_pages/features/player/view/audiobook_player.dart';
/// Builds the "shell" for the app by building a Scaffold with a /// Builds the "shell" for the app by building a Scaffold with a
@ -17,6 +20,16 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
// playerExpandProgress is used to animate bottom navigation bar to opacity 0 and slide down when player is expanded
// final playerProgress =
// useValueListenable(ref.watch(playerExpandProgressNotifierProvider));
final playerProgress = ref.watch(playerHeightProvider);
final playerMaxHeight = MediaQuery.of(context).size.height;
var percentExpanded = (playerProgress - playerMinHeight) /
(playerMaxHeight - playerMinHeight);
// Clamp the value between 0 and 1
percentExpanded = max(0, min(1, percentExpanded));
return Scaffold( return Scaffold(
body: Stack( body: Stack(
children: [ children: [
@ -24,33 +37,44 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
const AudiobookPlayer(), const AudiobookPlayer(),
], ],
), ),
bottomNavigationBar: BottomNavigationBar( bottomNavigationBar: Opacity(
elevation: 0.0, // Opacity is interpolated from 1 to 0 when player is expanded
landscapeLayout: BottomNavigationBarLandscapeLayout.centered, opacity: 1 - percentExpanded,
selectedFontSize: Theme.of(context).textTheme.labelMedium!.fontSize!, child: SizedBox(
unselectedFontSize: Theme.of(context).textTheme.labelMedium!.fontSize!, // height is interpolated from 0 to 56 when player is expanded
showUnselectedLabels: false, height: 56 * (1 - percentExpanded),
fixedColor: Theme.of(context).colorScheme.onBackground,
// type: BottomNavigationBarType.fixed,
// Here, the items of BottomNavigationBar are hard coded. In a real child: BottomNavigationBar(
// world scenario, the items would most likely be generated from the elevation: 0.0,
// branches of the shell route, which can be fetched using landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
// `navigationShell.route.branches`. selectedFontSize:
items: const <BottomNavigationBarItem>[ Theme.of(context).textTheme.labelMedium!.fontSize!,
BottomNavigationBarItem( unselectedFontSize:
label: 'Home', Theme.of(context).textTheme.labelMedium!.fontSize!,
icon: Icon(Icons.home_outlined), showUnselectedLabels: false,
activeIcon: Icon(Icons.home), fixedColor: Theme.of(context).colorScheme.onBackground,
// type: BottomNavigationBarType.fixed,
// Here, the items of BottomNavigationBar are hard coded. In a real
// world scenario, the items would most likely be generated from the
// branches of the shell route, which can be fetched using
// `navigationShell.route.branches`.
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
label: 'Home',
icon: Icon(Icons.home_outlined),
activeIcon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Settings',
icon: Icon(Icons.settings_outlined),
activeIcon: Icon(Icons.settings),
),
],
currentIndex: navigationShell.currentIndex,
onTap: (int index) => _onTap(context, index),
), ),
BottomNavigationBarItem( ),
label: 'Settings',
icon: Icon(Icons.settings_outlined),
activeIcon: Icon(Icons.settings),
),
],
currentIndex: navigationShell.currentIndex,
onTap: (int index) => _onTap(context, index),
), ),
); );
} }

View file

@ -13,8 +13,30 @@ class AppSettings with _$AppSettings {
const factory AppSettings({ const factory AppSettings({
@Default(true) bool isDarkMode, @Default(true) bool isDarkMode,
@Default(false) bool useMaterialThemeOnItemPage, @Default(false) bool useMaterialThemeOnItemPage,
@Default(PlayerSettings()) PlayerSettings playerSettings,
}) = _AppSettings; }) = _AppSettings;
factory AppSettings.fromJson(Map<String, dynamic> json) => factory AppSettings.fromJson(Map<String, dynamic> json) =>
_$AppSettingsFromJson(json); _$AppSettingsFromJson(json);
} }
@freezed
class PlayerSettings with _$PlayerSettings {
const factory PlayerSettings({
@Default(MinimizedPlayerSettings())
MinimizedPlayerSettings miniPlayerSettings,
}) = _PlayerSettings;
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
_$PlayerSettingsFromJson(json);
}
@freezed
class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
const factory MinimizedPlayerSettings({
@Default(false) bool useChapterInfo,
}) = _MiniPlayerSettings;
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
_$MinimizedPlayerSettingsFromJson(json);
}

View file

@ -22,6 +22,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) {
mixin _$AppSettings { mixin _$AppSettings {
bool get isDarkMode => throw _privateConstructorUsedError; bool get isDarkMode => throw _privateConstructorUsedError;
bool get useMaterialThemeOnItemPage => throw _privateConstructorUsedError; bool get useMaterialThemeOnItemPage => throw _privateConstructorUsedError;
PlayerSettings get playerSettings => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@ -35,7 +36,12 @@ abstract class $AppSettingsCopyWith<$Res> {
AppSettings value, $Res Function(AppSettings) then) = AppSettings value, $Res Function(AppSettings) then) =
_$AppSettingsCopyWithImpl<$Res, AppSettings>; _$AppSettingsCopyWithImpl<$Res, AppSettings>;
@useResult @useResult
$Res call({bool isDarkMode, bool useMaterialThemeOnItemPage}); $Res call(
{bool isDarkMode,
bool useMaterialThemeOnItemPage,
PlayerSettings playerSettings});
$PlayerSettingsCopyWith<$Res> get playerSettings;
} }
/// @nodoc /// @nodoc
@ -53,6 +59,7 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
$Res call({ $Res call({
Object? isDarkMode = null, Object? isDarkMode = null,
Object? useMaterialThemeOnItemPage = null, Object? useMaterialThemeOnItemPage = null,
Object? playerSettings = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
isDarkMode: null == isDarkMode isDarkMode: null == isDarkMode
@ -63,8 +70,20 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
? _value.useMaterialThemeOnItemPage ? _value.useMaterialThemeOnItemPage
: useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable : useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable
as bool, as bool,
playerSettings: null == playerSettings
? _value.playerSettings
: playerSettings // ignore: cast_nullable_to_non_nullable
as PlayerSettings,
) as $Val); ) as $Val);
} }
@override
@pragma('vm:prefer-inline')
$PlayerSettingsCopyWith<$Res> get playerSettings {
return $PlayerSettingsCopyWith<$Res>(_value.playerSettings, (value) {
return _then(_value.copyWith(playerSettings: value) as $Val);
});
}
} }
/// @nodoc /// @nodoc
@ -75,7 +94,13 @@ abstract class _$$AppSettingsImplCopyWith<$Res>
__$$AppSettingsImplCopyWithImpl<$Res>; __$$AppSettingsImplCopyWithImpl<$Res>;
@override @override
@useResult @useResult
$Res call({bool isDarkMode, bool useMaterialThemeOnItemPage}); $Res call(
{bool isDarkMode,
bool useMaterialThemeOnItemPage,
PlayerSettings playerSettings});
@override
$PlayerSettingsCopyWith<$Res> get playerSettings;
} }
/// @nodoc /// @nodoc
@ -91,6 +116,7 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
$Res call({ $Res call({
Object? isDarkMode = null, Object? isDarkMode = null,
Object? useMaterialThemeOnItemPage = null, Object? useMaterialThemeOnItemPage = null,
Object? playerSettings = null,
}) { }) {
return _then(_$AppSettingsImpl( return _then(_$AppSettingsImpl(
isDarkMode: null == isDarkMode isDarkMode: null == isDarkMode
@ -101,6 +127,10 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
? _value.useMaterialThemeOnItemPage ? _value.useMaterialThemeOnItemPage
: useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable : useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable
as bool, as bool,
playerSettings: null == playerSettings
? _value.playerSettings
: playerSettings // ignore: cast_nullable_to_non_nullable
as PlayerSettings,
)); ));
} }
} }
@ -109,7 +139,9 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
@JsonSerializable() @JsonSerializable()
class _$AppSettingsImpl implements _AppSettings { class _$AppSettingsImpl implements _AppSettings {
const _$AppSettingsImpl( const _$AppSettingsImpl(
{this.isDarkMode = true, this.useMaterialThemeOnItemPage = false}); {this.isDarkMode = true,
this.useMaterialThemeOnItemPage = false,
this.playerSettings = const PlayerSettings()});
factory _$AppSettingsImpl.fromJson(Map<String, dynamic> json) => factory _$AppSettingsImpl.fromJson(Map<String, dynamic> json) =>
_$$AppSettingsImplFromJson(json); _$$AppSettingsImplFromJson(json);
@ -120,10 +152,13 @@ class _$AppSettingsImpl implements _AppSettings {
@override @override
@JsonKey() @JsonKey()
final bool useMaterialThemeOnItemPage; final bool useMaterialThemeOnItemPage;
@override
@JsonKey()
final PlayerSettings playerSettings;
@override @override
String toString() { String toString() {
return 'AppSettings(isDarkMode: $isDarkMode, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage)'; return 'AppSettings(isDarkMode: $isDarkMode, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage, playerSettings: $playerSettings)';
} }
@override @override
@ -136,13 +171,15 @@ class _$AppSettingsImpl implements _AppSettings {
(identical(other.useMaterialThemeOnItemPage, (identical(other.useMaterialThemeOnItemPage,
useMaterialThemeOnItemPage) || useMaterialThemeOnItemPage) ||
other.useMaterialThemeOnItemPage == other.useMaterialThemeOnItemPage ==
useMaterialThemeOnItemPage)); useMaterialThemeOnItemPage) &&
(identical(other.playerSettings, playerSettings) ||
other.playerSettings == playerSettings));
} }
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
int get hashCode => int get hashCode => Object.hash(
Object.hash(runtimeType, isDarkMode, useMaterialThemeOnItemPage); runtimeType, isDarkMode, useMaterialThemeOnItemPage, playerSettings);
@JsonKey(ignore: true) @JsonKey(ignore: true)
@override @override
@ -161,7 +198,8 @@ class _$AppSettingsImpl implements _AppSettings {
abstract class _AppSettings implements AppSettings { abstract class _AppSettings implements AppSettings {
const factory _AppSettings( const factory _AppSettings(
{final bool isDarkMode, {final bool isDarkMode,
final bool useMaterialThemeOnItemPage}) = _$AppSettingsImpl; final bool useMaterialThemeOnItemPage,
final PlayerSettings playerSettings}) = _$AppSettingsImpl;
factory _AppSettings.fromJson(Map<String, dynamic> json) = factory _AppSettings.fromJson(Map<String, dynamic> json) =
_$AppSettingsImpl.fromJson; _$AppSettingsImpl.fromJson;
@ -171,7 +209,309 @@ abstract class _AppSettings implements AppSettings {
@override @override
bool get useMaterialThemeOnItemPage; bool get useMaterialThemeOnItemPage;
@override @override
PlayerSettings get playerSettings;
@override
@JsonKey(ignore: true) @JsonKey(ignore: true)
_$$AppSettingsImplCopyWith<_$AppSettingsImpl> get copyWith => _$$AppSettingsImplCopyWith<_$AppSettingsImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
PlayerSettings _$PlayerSettingsFromJson(Map<String, dynamic> json) {
return _PlayerSettings.fromJson(json);
}
/// @nodoc
mixin _$PlayerSettings {
MinimizedPlayerSettings get miniPlayerSettings =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PlayerSettingsCopyWith<PlayerSettings> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PlayerSettingsCopyWith<$Res> {
factory $PlayerSettingsCopyWith(
PlayerSettings value, $Res Function(PlayerSettings) then) =
_$PlayerSettingsCopyWithImpl<$Res, PlayerSettings>;
@useResult
$Res call({MinimizedPlayerSettings miniPlayerSettings});
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
}
/// @nodoc
class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
implements $PlayerSettingsCopyWith<$Res> {
_$PlayerSettingsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? miniPlayerSettings = null,
}) {
return _then(_value.copyWith(
miniPlayerSettings: null == miniPlayerSettings
? _value.miniPlayerSettings
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
as MinimizedPlayerSettings,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings {
return $MinimizedPlayerSettingsCopyWith<$Res>(_value.miniPlayerSettings,
(value) {
return _then(_value.copyWith(miniPlayerSettings: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$PlayerSettingsImplCopyWith<$Res>
implements $PlayerSettingsCopyWith<$Res> {
factory _$$PlayerSettingsImplCopyWith(_$PlayerSettingsImpl value,
$Res Function(_$PlayerSettingsImpl) then) =
__$$PlayerSettingsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({MinimizedPlayerSettings miniPlayerSettings});
@override
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
}
/// @nodoc
class __$$PlayerSettingsImplCopyWithImpl<$Res>
extends _$PlayerSettingsCopyWithImpl<$Res, _$PlayerSettingsImpl>
implements _$$PlayerSettingsImplCopyWith<$Res> {
__$$PlayerSettingsImplCopyWithImpl(
_$PlayerSettingsImpl _value, $Res Function(_$PlayerSettingsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? miniPlayerSettings = null,
}) {
return _then(_$PlayerSettingsImpl(
miniPlayerSettings: null == miniPlayerSettings
? _value.miniPlayerSettings
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
as MinimizedPlayerSettings,
));
}
}
/// @nodoc
@JsonSerializable()
class _$PlayerSettingsImpl implements _PlayerSettings {
const _$PlayerSettingsImpl(
{this.miniPlayerSettings = const MinimizedPlayerSettings()});
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
_$$PlayerSettingsImplFromJson(json);
@override
@JsonKey()
final MinimizedPlayerSettings miniPlayerSettings;
@override
String toString() {
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$PlayerSettingsImpl &&
(identical(other.miniPlayerSettings, miniPlayerSettings) ||
other.miniPlayerSettings == miniPlayerSettings));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, miniPlayerSettings);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
__$$PlayerSettingsImplCopyWithImpl<_$PlayerSettingsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$PlayerSettingsImplToJson(
this,
);
}
}
abstract class _PlayerSettings implements PlayerSettings {
const factory _PlayerSettings(
{final MinimizedPlayerSettings miniPlayerSettings}) =
_$PlayerSettingsImpl;
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
_$PlayerSettingsImpl.fromJson;
@override
MinimizedPlayerSettings get miniPlayerSettings;
@override
@JsonKey(ignore: true)
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
throw _privateConstructorUsedError;
}
MinimizedPlayerSettings _$MinimizedPlayerSettingsFromJson(
Map<String, dynamic> json) {
return _MiniPlayerSettings.fromJson(json);
}
/// @nodoc
mixin _$MinimizedPlayerSettings {
bool get useChapterInfo => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$MinimizedPlayerSettingsCopyWith<MinimizedPlayerSettings> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $MinimizedPlayerSettingsCopyWith<$Res> {
factory $MinimizedPlayerSettingsCopyWith(MinimizedPlayerSettings value,
$Res Function(MinimizedPlayerSettings) then) =
_$MinimizedPlayerSettingsCopyWithImpl<$Res, MinimizedPlayerSettings>;
@useResult
$Res call({bool useChapterInfo});
}
/// @nodoc
class _$MinimizedPlayerSettingsCopyWithImpl<$Res,
$Val extends MinimizedPlayerSettings>
implements $MinimizedPlayerSettingsCopyWith<$Res> {
_$MinimizedPlayerSettingsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? useChapterInfo = null,
}) {
return _then(_value.copyWith(
useChapterInfo: null == useChapterInfo
? _value.useChapterInfo
: useChapterInfo // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$MiniPlayerSettingsImplCopyWith<$Res>
implements $MinimizedPlayerSettingsCopyWith<$Res> {
factory _$$MiniPlayerSettingsImplCopyWith(_$MiniPlayerSettingsImpl value,
$Res Function(_$MiniPlayerSettingsImpl) then) =
__$$MiniPlayerSettingsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({bool useChapterInfo});
}
/// @nodoc
class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
extends _$MinimizedPlayerSettingsCopyWithImpl<$Res,
_$MiniPlayerSettingsImpl>
implements _$$MiniPlayerSettingsImplCopyWith<$Res> {
__$$MiniPlayerSettingsImplCopyWithImpl(_$MiniPlayerSettingsImpl _value,
$Res Function(_$MiniPlayerSettingsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? useChapterInfo = null,
}) {
return _then(_$MiniPlayerSettingsImpl(
useChapterInfo: null == useChapterInfo
? _value.useChapterInfo
: useChapterInfo // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
const _$MiniPlayerSettingsImpl({this.useChapterInfo = false});
factory _$MiniPlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
_$$MiniPlayerSettingsImplFromJson(json);
@override
@JsonKey()
final bool useChapterInfo;
@override
String toString() {
return 'MinimizedPlayerSettings(useChapterInfo: $useChapterInfo)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$MiniPlayerSettingsImpl &&
(identical(other.useChapterInfo, useChapterInfo) ||
other.useChapterInfo == useChapterInfo));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, useChapterInfo);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
__$$MiniPlayerSettingsImplCopyWithImpl<_$MiniPlayerSettingsImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$MiniPlayerSettingsImplToJson(
this,
);
}
}
abstract class _MiniPlayerSettings implements MinimizedPlayerSettings {
const factory _MiniPlayerSettings({final bool useChapterInfo}) =
_$MiniPlayerSettingsImpl;
factory _MiniPlayerSettings.fromJson(Map<String, dynamic> json) =
_$MiniPlayerSettingsImpl.fromJson;
@override
bool get useChapterInfo;
@override
@JsonKey(ignore: true)
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -11,10 +11,41 @@ _$AppSettingsImpl _$$AppSettingsImplFromJson(Map<String, dynamic> json) =>
isDarkMode: json['isDarkMode'] as bool? ?? true, isDarkMode: json['isDarkMode'] as bool? ?? true,
useMaterialThemeOnItemPage: useMaterialThemeOnItemPage:
json['useMaterialThemeOnItemPage'] as bool? ?? false, json['useMaterialThemeOnItemPage'] as bool? ?? false,
playerSettings: json['playerSettings'] == null
? const PlayerSettings()
: PlayerSettings.fromJson(
json['playerSettings'] as Map<String, dynamic>),
); );
Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) => Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) =>
<String, dynamic>{ <String, dynamic>{
'isDarkMode': instance.isDarkMode, 'isDarkMode': instance.isDarkMode,
'useMaterialThemeOnItemPage': instance.useMaterialThemeOnItemPage, 'useMaterialThemeOnItemPage': instance.useMaterialThemeOnItemPage,
'playerSettings': instance.playerSettings,
};
_$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map<String, dynamic> json) =>
_$PlayerSettingsImpl(
miniPlayerSettings: json['miniPlayerSettings'] == null
? const MinimizedPlayerSettings()
: MinimizedPlayerSettings.fromJson(
json['miniPlayerSettings'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$PlayerSettingsImplToJson(
_$PlayerSettingsImpl instance) =>
<String, dynamic>{
'miniPlayerSettings': instance.miniPlayerSettings,
};
_$MiniPlayerSettingsImpl _$$MiniPlayerSettingsImplFromJson(
Map<String, dynamic> json) =>
_$MiniPlayerSettingsImpl(
useChapterInfo: json['useChapterInfo'] as bool? ?? false,
);
Map<String, dynamic> _$$MiniPlayerSettingsImplToJson(
_$MiniPlayerSettingsImpl instance) =>
<String, dynamic>{
'useChapterInfo': instance.useChapterInfo,
}; };

View file

@ -91,6 +91,8 @@ set_target_properties(${BINARY_NAME}
# them to the application. # them to the application.
include(flutter/generated_plugins.cmake) include(flutter/generated_plugins.cmake)
# as suggested by https://pub.dev/packages/just_audio_media_kit
target_link_libraries(${BINARY_NAME} PRIVATE ${MIMALLOC_LIB})
# === Installation === # === Installation ===
# By default, "installing" just makes a relocatable bundle in the build # By default, "installing" just makes a relocatable bundle in the build

View file

@ -6,17 +6,17 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h> #include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin");
isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar);
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View file

@ -3,8 +3,8 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
isar_flutter_libs isar_flutter_libs
media_kit_libs_linux
url_launcher_linux url_launcher_linux
) )

View file

@ -65,6 +65,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.11.0"
audio_service:
dependency: transitive
description:
name: audio_service
sha256: "4547c312a94f9cb2c48b60823fb190767cbd63454a83c73049384d5d3cba4650"
url: "https://pub.dev"
source: hosted
version: "0.18.13"
audio_service_platform_interface:
dependency: transitive
description:
name: audio_service_platform_interface
sha256: "8431a455dac9916cc9ee6f7da5620a666436345c906ad2ebb7fa41d18b3c1bf4"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
audio_service_web:
dependency: transitive
description:
name: audio_service_web
sha256: "9d7d5ae5f98a5727f2580fad73062f2484f400eef6cef42919413268e62a363e"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
audio_session:
dependency: "direct main"
description:
name: audio_session
sha256: a49af9981eec5d7cd73b37bacb6ee73f8143a6a9f9bd5b6021e6c346b9b6cf4e
url: "https://pub.dev"
source: hosted
version: "0.1.19"
audio_video_progress_bar: audio_video_progress_bar:
dependency: "direct main" dependency: "direct main"
description: description:
@ -73,62 +105,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
audioplayers:
dependency: "direct main"
description:
name: audioplayers
sha256: "752039d6aa752597c98ec212e9759519061759e402e7da59a511f39d43aa07d2"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
audioplayers_android:
dependency: transitive
description:
name: audioplayers_android
sha256: de576b890befe27175c2f511ba8b742bec83765fa97c3ce4282bba46212f58e4
url: "https://pub.dev"
source: hosted
version: "5.0.0"
audioplayers_darwin:
dependency: transitive
description:
name: audioplayers_darwin
sha256: e507887f3ff18d8e5a10a668d7bedc28206b12e10b98347797257c6ae1019c3b
url: "https://pub.dev"
source: hosted
version: "6.0.0"
audioplayers_linux:
dependency: transitive
description:
name: audioplayers_linux
sha256: "3d3d244c90436115417f170426ce768856d8fe4dfc5ed66a049d2890acfa82f9"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
audioplayers_platform_interface:
dependency: transitive
description:
name: audioplayers_platform_interface
sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
audioplayers_web:
dependency: transitive
description:
name: audioplayers_web
sha256: db8fc420dadf80da18e2286c18e746fb4c3b2c5adbf0c963299dde046828886d
url: "https://pub.dev"
source: hosted
version: "5.0.0"
audioplayers_windows:
dependency: transitive
description:
name: audioplayers_windows
sha256: "8605762dddba992138d476f6a0c3afd9df30ac5b96039929063eceed416795c2"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
auto_scroll_text: auto_scroll_text:
dependency: "direct main" dependency: "direct main"
description: description:
@ -568,6 +544,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev"
source: hosted
version: "4.1.7"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -616,6 +600,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.8.0" version: "6.8.0"
just_audio:
dependency: "direct main"
description:
name: just_audio
sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b
url: "https://pub.dev"
source: hosted
version: "0.9.37"
just_audio_background:
dependency: "direct main"
description:
name: just_audio_background
sha256: "3454ffc97edfa1282b7f42759bfa8aa13d9114a24465f4101e0d3ae58a9327fb"
url: "https://pub.dev"
source: hosted
version: "0.0.1-beta.11"
just_audio_media_kit:
dependency: "direct main"
description:
name: just_audio_media_kit
sha256: bbecbd43959c230d9f9610df0e0165855e711b4c960ce730c08f31107cc3bd26
url: "https://pub.dev"
source: hosted
version: "2.0.4"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1
url: "https://pub.dev"
source: hosted
version: "4.2.2"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70"
url: "https://pub.dev"
source: hosted
version: "0.4.9"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -680,6 +704,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.0" version: "0.8.0"
media_kit:
dependency: transitive
description:
name: media_kit
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
url: "https://pub.dev"
source: hosted
version: "1.1.10+1"
media_kit_libs_linux:
dependency: "direct main"
description:
name: media_kit_libs_linux
sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310
url: "https://pub.dev"
source: hosted
version: "1.1.3"
media_kit_libs_windows_audio:
dependency: "direct main"
description:
name: media_kit_libs_windows_audio
sha256: c2fd558cc87b9d89a801141fcdffe02e338a3b21a41a18fbd63d5b221a1b8e53
url: "https://pub.dev"
source: hosted
version: "1.0.9"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -699,11 +747,10 @@ packages:
miniplayer: miniplayer:
dependency: "direct main" dependency: "direct main"
description: description:
name: miniplayer path: "../miniplayer"
sha256: "6e12c27aef7432fc16508460a6dc824f3edfeb01761bd0dbfbccc84d516121bf" relative: true
url: "https://pub.dev" source: path
source: hosted version: "1.0.3"
version: "1.0.1"
octo_image: octo_image:
dependency: transitive dependency: transitive
description: description:
@ -776,6 +823,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -857,13 +912,21 @@ packages:
source: hosted source: hosted
version: "2.3.10" version: "2.3.10"
rxdart: rxdart:
dependency: transitive dependency: "direct main"
description: description:
name: rxdart name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.27.7" version: "0.27.7"
safe_local_storage:
dependency: transitive
description:
name: safe_local_storage
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440
url: "https://pub.dev"
source: hosted
version: "1.0.2"
scroll_loop_auto_scroll: scroll_loop_auto_scroll:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1052,6 +1115,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
url: "https://pub.dev"
source: hosted
version: "1.0.0+1"
uri_parser:
dependency: transitive
description:
name: uri_parser
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1180,6 +1259,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

View file

@ -32,8 +32,8 @@ isar_version: &isar_version ^4.0.0-dev.13 # define the version to be used
dependencies: dependencies:
animated_list_plus: ^0.5.2 animated_list_plus: ^0.5.2
animated_theme_switcher: ^2.0.10 animated_theme_switcher: ^2.0.10
audio_session: ^0.1.19
audio_video_progress_bar: ^2.0.2 audio_video_progress_bar: ^2.0.2
audioplayers: ^6.0.0
auto_scroll_text: ^0.0.7 auto_scroll_text: ^0.0.7
cached_network_image: ^3.3.1 cached_network_image: ^3.3.1
coast: ^2.0.2 coast: ^2.0.2
@ -54,11 +54,18 @@ dependencies:
isar: ^4.0.0-dev.13 isar: ^4.0.0-dev.13
isar_flutter_libs: ^4.0.0-dev.13 isar_flutter_libs: ^4.0.0-dev.13
json_annotation: ^4.9.0 json_annotation: ^4.9.0
just_audio: ^0.9.37
just_audio_background: ^0.0.1-beta.11
just_audio_media_kit: ^2.0.4
lottie: ^3.1.0 lottie: ^3.1.0
miniplayer: ^1.0.1 media_kit_libs_linux: any
media_kit_libs_windows_audio: any
miniplayer:
path: ../miniplayer
path: ^1.9.0 path: ^1.9.0
path_provider: ^2.1.0 path_provider: ^2.1.0
riverpod_annotation: ^2.3.5 riverpod_annotation: ^2.3.5
rxdart: ^0.27.7
scroll_loop_auto_scroll: ^0.0.5 scroll_loop_auto_scroll: ^0.0.5
shelfsdk: shelfsdk:
path: ../../_dart/shelfsdk path: ../../_dart/shelfsdk

View file

@ -6,15 +6,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h> #include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <media_kit_libs_windows_audio/media_kit_libs_windows_audio_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
IsarFlutterLibsPluginRegisterWithRegistrar( IsarFlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("MediaKitLibsWindowsAudioPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View file

@ -3,8 +3,8 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
isar_flutter_libs isar_flutter_libs
media_kit_libs_windows_audio
url_launcher_windows url_launcher_windows
) )