Skip to content

Commit

Permalink
Feature: Queue loop #234
Browse files Browse the repository at this point in the history
  • Loading branch information
anandnet committed Oct 6, 2024
1 parent df7ef72 commit 9a56bee
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 50 deletions.
8 changes: 7 additions & 1 deletion lib/services/audio_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
late String? currentSongUrl;
bool isPlayingUsingLockCachingSource = false;
bool loopModeEnabled = false;
bool queueLoopModeEnabled = false;
bool shuffleModeEnabled = false;
bool loudnessNormalizationEnabled = false;
// var networkErrorPause = false;
Expand Down Expand Up @@ -80,6 +81,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
_player.setSkipSilenceEnabled(appPrefsBox.get("skipSilenceEnabled"));
loopModeEnabled = appPrefsBox.get("isLoopModeEnabled") ?? false;
shuffleModeEnabled = appPrefsBox.get("isShuffleModeEnabled") ?? false;
queueLoopModeEnabled = Hive.box("AppPrefs").get("queueLoopModeEnabled") ?? false;
loudnessNormalizationEnabled =
appPrefsBox.get("loudnessNormalizationEnabled") ?? false;
_listenForDurationChanges();
Expand Down Expand Up @@ -358,6 +360,8 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {

if (queue.value.length > currentIndex + 1) {
return currentIndex + 1;
} else if (queueLoopModeEnabled) {
return 0;
} else {
return currentIndex;
}
Expand Down Expand Up @@ -597,7 +601,7 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
final currentQueue = queue.value;
currentQueue.insert(currentIndex + 1, song);
queue.add(currentQueue);
if(shuffleModeEnabled){
if (shuffleModeEnabled) {
shuffledQueue.insert(currentShuffleIndex + 1, song.id);
}
} else if (name == 'openEqualizer') {
Expand All @@ -614,6 +618,8 @@ class MyAudioHandler extends BaseAudioHandler with GetxServiceMixin {
final songIndex = extras!['index'];
currentIndex = songIndex;
mediaItem.add(queue.value[currentIndex]);
} else if (name == "toggleQueueLoopMode") {
queueLoopModeEnabled = extras!['enable'];
}
}

Expand Down
63 changes: 48 additions & 15 deletions lib/ui/home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,54 @@ class Home extends StatelessWidget {
.textTheme
.titleLarge,
),
IconButton(
onPressed: () {
if (playerController
.isShuffleModeEnabled.isTrue) {
ScaffoldMessenger.of(context)
.showSnackBar(snackbar(
context,
"queueShufflingDeniedMsg"
.tr,
size: SanckBarSize.BIG));
return;
}
playerController.shuffleQueue();
},
icon: const Icon(Icons.shuffle))
Row(
children: [
InkWell(
onTap: () {
playerController
.toggleQueueLoopMode();
},
child: Obx(
() => Container(
height: 30,
padding:
const EdgeInsets.symmetric(
horizontal: 20),
decoration: BoxDecoration(
color: playerController
.isQueueLoopModeEnabled
.isFalse
? Colors.white24
: Colors.white
.withOpacity(0.8),
borderRadius:
BorderRadius.circular(20),
),
child: Center(
child:
Text("queueLoop".tr)),
),
),
),
IconButton(
onPressed: () {
if (playerController
.isShuffleModeEnabled
.isTrue) {
ScaffoldMessenger.of(context)
.showSnackBar(snackbar(
context,
"queueShufflingDeniedMsg"
.tr,
size: SanckBarSize
.BIG));
return;
}
playerController.shuffleQueue();
},
icon: const Icon(Icons.shuffle)),
],
)
],
),
)),
Expand Down
10 changes: 7 additions & 3 deletions lib/ui/player/components/mini_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:hive/hive.dart';
import 'package:ionicons/ionicons.dart';
import 'package:widget_marquee/widget_marquee.dart';

import '/utils/helper.dart';
import '/ui/widgets/lyrics_dialog.dart';
import '/ui/widgets/song_info_dialog.dart';
import '/ui/player/player_controller.dart';
Expand Down Expand Up @@ -246,9 +247,12 @@ class MiniPlayer extends StatelessWidget {
child: Obx(() {
final isLastSong =
playerController.currentQueue.isEmpty ||
(playerController
.isShuffleModeEnabled
.isFalse &&
(!(playerController
.isShuffleModeEnabled
.isTrue ||
playerController
.isQueueLoopModeEnabled
.isTrue) &&
(playerController
.currentQueue.last.id ==
playerController.currentSong
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/player/components/player_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ class PlayerControlWidget extends StatelessWidget {
Widget _nextButton(PlayerController playerController, BuildContext context) {
return Obx(() {
final isLastSong = playerController.currentQueue.isEmpty ||
(playerController.isShuffleModeEnabled.isFalse &&
(!(playerController.isShuffleModeEnabled.isTrue ||
playerController.isQueueLoopModeEnabled.isTrue) &&
(playerController.currentQueue.last.id ==
playerController.currentSong.value?.id));
return IconButton(
Expand Down
118 changes: 90 additions & 28 deletions lib/ui/player/player.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'dart:ui';

import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:harmonymusic/ui/player/components/gesture_player.dart';
import 'package:harmonymusic/ui/player/components/standard_player.dart';
import 'package:harmonymusic/ui/screens/Settings/settings_screen_controller.dart';

import '/ui/player/components/gesture_player.dart';
import '/ui/player/components/standard_player.dart';
import '/ui/screens/Settings/settings_screen_controller.dart';
import '../../utils/helper.dart';
import '../widgets/snackbar.dart';
import '../widgets/up_next_queue.dart';
Expand Down Expand Up @@ -65,31 +67,91 @@ class Player extends StatelessWidget {
onReorderEnd: onReorderEnd,
onReorderStart: onReorderStart,
),
Positioned(
bottom: 60,
right: 15,
child: SizedBox(
height: 60,
width: 60,
child: FittedBox(
child: FloatingActionButton(
focusElevation: 0,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(14))),
elevation: 0,
onPressed: () {
if (playerController
.isShuffleModeEnabled.isTrue) {
ScaffoldMessenger.of(context)
.showSnackBar(snackbar(context,
"queueShufflingDeniedMsg".tr,
size: SanckBarSize.BIG));
return;
}
playerController.shuffleQueue();
},
child: const Icon(Icons.shuffle))))),
Align(
alignment: Alignment.bottomCenter,
child: ClipRRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
padding: const EdgeInsets.only(
bottom: 10, left: 10, right: 10),
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
blurRadius: 5, color: Colors.black54)
],
color: Theme.of(context)
.primaryColor
.withOpacity(0.5)),
height: 60 + Get.mediaQuery.padding.bottom,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
//queue loop button and queue shuffle button
Obx(
() => Text(
"${playerController.currentQueue.length} ${"songs".tr}",
style: Theme.of(context)
.textTheme
.titleSmall!
.copyWith(
color: Theme.of(context)
.textTheme
.titleMedium!
.color),
),
),
InkWell(
onTap: () {
if (playerController
.isShuffleModeEnabled.isTrue) {
ScaffoldMessenger.of(context)
.showSnackBar(snackbar(context,
"queueShufflingDeniedMsg".tr,
size: SanckBarSize.BIG));
return;
}
playerController.shuffleQueue();
},
child: Container(
height: 30,
padding: const EdgeInsets.symmetric(
horizontal: 20),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.circular(20),
),
child:
Center(child: Text("shuffleQueue".tr)),
),
),
InkWell(
onTap: () {
playerController.toggleQueueLoopMode();
},
child: Obx(
() => Container(
height: 30,
padding: const EdgeInsets.symmetric(
horizontal: 20),
decoration: BoxDecoration(
color: playerController
.isQueueLoopModeEnabled.isFalse
? Colors.white24
: Colors.white.withOpacity(0.8),
borderRadius: BorderRadius.circular(20),
),
child:
Center(child: Text("queueLoop".tr)),
),
),
),
],
),
),
),
),
),
],
);
},
Expand Down
44 changes: 43 additions & 1 deletion lib/ui/player/player_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class PlayerController extends GetxController
final currentSongIndex = (0).obs;
final isFirstSong = true;
final isLastSong = true;
final isQueueLoopModeEnabled = false.obs;
final isLoopModeEnabled = false.obs;
final isShuffleModeEnabled = false.obs;
final currentSong = Rxn<MediaItem>();
Expand Down Expand Up @@ -103,6 +104,9 @@ class PlayerController extends GetxController
Hive.box("AppPrefs").get("isLoopModeEnabled") ?? false;
isShuffleModeEnabled.value =
Hive.box("appPrefs").get("isShuffleModeEnabled") ?? false;
isQueueLoopModeEnabled.value =
Hive.box("AppPrefs").get("queueLoopModeEnabled") ?? false;

if (GetPlatform.isDesktop) {
setVolume(Hive.box("AppPrefs").get("volume") ?? 100);
}
Expand Down Expand Up @@ -319,6 +323,13 @@ class PlayerController extends GetxController
_playerPanelCheck();
await _audioHandler
.customAction("setSourceNPlay", {'mediaItem': mediaItem});

// disable queue loop mode when radio is started
if (radio &&
isQueueLoopModeEnabled.isTrue &&
isShuffleModeEnabled.isFalse) {
toggleQueueLoopMode();
}
}

Future<void> playPlayListSong(List<MediaItem> mediaItems, int index) async {
Expand Down Expand Up @@ -362,7 +373,7 @@ class PlayerController extends GetxController
///enqueueSong append a song to current queue
///if current queue is empty, push the song into Queue and play that song
Future<void> enqueueSong(MediaItem mediaItem) async {
if (currentQueue.isEmpty) {
if (currentQueue.isEmpty) {
await playPlayListSong([mediaItem], 0);
return;
}
Expand Down Expand Up @@ -452,6 +463,13 @@ class PlayerController extends GetxController
: _audioHandler.setShuffleMode(AudioServiceShuffleMode.all);
isShuffleModeEnabled.value = !shuffleModeEnabled;
await Hive.box("AppPrefs").put("isShuffleModeEnabled", !shuffleModeEnabled);
// restrict queue loop mode when shuffle mode is enabled
if (isShuffleModeEnabled.isTrue && isQueueLoopModeEnabled.isFalse) {
isQueueLoopModeEnabled.value = true;
} else if (isShuffleModeEnabled.isFalse) {
isQueueLoopModeEnabled.value =
Hive.box("AppPrefs").get("queueLoopModeEnabled", defaultValue: false);
}
}

void onReorder(int oldIndex, int newIndex) {
Expand Down Expand Up @@ -521,6 +539,30 @@ class PlayerController extends GetxController
.put("isLoopModeEnabled", isLoopModeEnabled.value);
}

Future<void> toggleQueueLoopMode({bool showMessage = true}) async {
if (isShuffleModeEnabled.isTrue && isQueueLoopModeEnabled.isTrue) {
if (!showMessage) return;
ScaffoldMessenger.of(Get.context!).showSnackBar(snackbar(
Get.context!, "queueLoopNotDisMsg1".tr,
size: SanckBarSize.BIG, duration: const Duration(seconds: 2)));
return;
}

if (isRadioModeOn && isQueueLoopModeEnabled.isFalse) {
if (!showMessage) return;
ScaffoldMessenger.of(Get.context!).showSnackBar(snackbar(
Get.context!, "queueLoopNotDisMsg2".tr,
size: SanckBarSize.BIG, duration: const Duration(seconds: 2)));
return;
}

isQueueLoopModeEnabled.value = !isQueueLoopModeEnabled.value;
await _audioHandler.customAction(
"toggleQueueLoopMode", {"enable": isQueueLoopModeEnabled.value});
await Hive.box("AppPrefs")
.put("queueLoopModeEnabled", isQueueLoopModeEnabled.value);
}

Future<void> setVolume(int value) async {
_audioHandler.customAction("setVolume", {"value": value});
volume.value = value;
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/widgets/up_next_queue.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class UpNextQueue extends StatelessWidget {
itemCount: playerController.currentQueue.length,
padding: EdgeInsets.only(
top: isQueueInSlidePanel ? 55 : 0,
bottom: Get.mediaQuery.padding.bottom),
bottom:
isQueueInSlidePanel ? 80 : 0 + Get.mediaQuery.padding.bottom),
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
final homeScaffoldContext =
Expand Down
4 changes: 4 additions & 0 deletions localization/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
"queuerearrangingDeniedMsg": "Queue can't be rearranged when shuffle mode is enabled",
"songNotPlayable": "Song is not playable due to server restriction!",
"upNext": "Up Next",
"shuffleQueue": "Shuffle Queue",
"queueLoop": "Queue loop",
"queueLoopNotDisMsg1": "Queue loop mode cannot be disabled when shuffle mode is enabled.",
"queueLoopNotDisMsg2": "Queue loop mode cannot be enabled in radio mode.",
"removeFromLib": "Remove from Library Songs",
"sleepTimer": "Sleep Timer",
"add5Minutes": "Add 5 minutes",
Expand Down

0 comments on commit 9a56bee

Please sign in to comment.