From 68457aa9856c034cc015c666ca2a5c4ec616e493 Mon Sep 17 00:00:00 2001 From: MSOB7YY Date: Sat, 27 Jul 2024 23:29:37 +0300 Subject: [PATCH] chore: soomee some some --- .github/workflows/dart.yml | 9 ++- lib/base/audio_handler.dart | 75 ++++++++++++++++--- lib/controller/json_to_history_parser.dart | 4 +- lib/controller/notification_controller.dart | 50 +++++-------- lib/controller/player_controller.dart | 4 +- lib/controller/search_sort_controller.dart | 2 +- lib/main.dart | 3 +- lib/ui/dialogs/general_popup_dialog.dart | 51 ------------- lib/ui/widgets/custom_widgets.dart | 6 +- .../controller/youtube_controller.dart | 37 ++++++--- .../user/youtube_account_manage_page.dart | 1 + pubspec.yaml | 4 +- 12 files changed, 129 insertions(+), 117 deletions(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index d421d8fc..4ee1109d 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -107,9 +107,9 @@ jobs: done <<< "$COMMITS" # Encode special characters - CHANGELOG="${CHANGELOG//'%'/'%25'}" - CHANGELOG="${CHANGELOG//$'\n'/'%0A'}" - CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" + CHANGELOG="${CHANGELOG//$'%'/%25}" + CHANGELOG="${CHANGELOG//$'\n'/%0A}" + CHANGELOG="${CHANGELOG//$'\r'/%0D}" echo "CHANGELOG=$CHANGELOG" >> $GITHUB_ENV - name: Creating Snapshot Release at namida-snapshots @@ -119,7 +119,8 @@ jobs: make_latest: true draft: false tag_name: ${{ steps.extract_version.outputs.version}} - body: ${{ env.CHANGELOG}} + body: | + ${{ env.CHANGELOG }} files: | build_final/* token: ${{ secrets.SNAPSHOTS_REPO_SECRET }} diff --git a/lib/base/audio_handler.dart b/lib/base/audio_handler.dart index 46cc7d18..1ca01fda 100644 --- a/lib/base/audio_handler.dart +++ b/lib/base/audio_handler.dart @@ -592,14 +592,67 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { Future setVideoLockCache(VideoStream stream) async { final url = stream.buildUrl(); if (url == null) throw Exception('null url'); - await setVideoSource( + + final cachedAudioPath = currentCachedAudio.value?.file.path; + final curritem = currentItem.value; + + AudioVideoSource? activeAudioSource; + if (cachedAudioPath != null) { + activeAudioSource = AudioVideoSource.file( + cachedAudioPath, + tag: curritem is YoutubeID + ? curritem.toMediaItem( + videoId, + _ytNotificationVideoInfo, + _ytNotificationVideoThumbnail, + currentIndex.value, + currentQueue.value.length, + currentItemDuration.value, + ) + : null, + ); + } else { + AudioStream? audioStream = currentAudioStream.value; + if (audioStream == null) { + final streamRes = YoutubeInfoController.video.fetchVideoStreamsSync(videoId)?.audioStreams; + if (streamRes != null) audioStream = YoutubeController.inst.getPreferredAudioStream(streamRes); + } + if (audioStream != null) { + final url = audioStream.buildUrl(); + if (url != null) { + activeAudioSource = LockCachingAudioSource( + url, + cacheFile: File(audioStream.cachePath(videoId)), + tag: mediaItem, + onCacheDone: (cacheFile) async => await _onAudioCacheDone(videoId, cacheFile), + ); + } + } + } + final videoOptions = VideoSourceOptions( source: LockCachingVideoSource( url, cacheFile: File(stream.cachePath(videoId)), onCacheDone: (cacheFile) async => await _onVideoCacheDone(videoId, cacheFile), ), - cacheKey: stream.cacheKey(videoId), + loop: false, + videoOnly: false, ); + // -- setting completely new source is needed as a workaround to internal source error + // where settings LockCachingVideoSource only throws source_not_found exception. + // -- its not likely for activeAudioSource to be null but just in case + activeAudioSource != null + ? await setSource( + activeAudioSource, + item: curritem, + startPlaying: () => wasPlaying, + videoOptions: videoOptions, + keepOldVideoSource: true, + cachedAudioPath: cachedAudioPath, + ) + : await setVideoSource( + source: videoOptions.source, + ); refreshNotification(); } @@ -1065,8 +1118,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { if (checkInterrupted()) return; final prefferedVideoStream = _isAudioOnlyPlayback || videoStreams.isEmpty ? null : YoutubeController.inst.getPreferredStreamQuality(videoStreams, preferIncludeWebm: false); - final prefferedAudioStream = - audiostreams.firstWhereEff((e) => !e.isWebm && e.audioTrack?.langCode == 'en') ?? audiostreams.firstWhereEff((e) => !e.isWebm) ?? audiostreams.firstOrNull; + final prefferedAudioStream = YoutubeController.inst.getPreferredAudioStream(audiostreams); final prefferedAudioStreamUrl = prefferedAudioStream?.buildUrl(); final prefferedVideoStreamUrl = prefferedVideoStream?.buildUrl(); if (prefferedAudioStreamUrl != null || prefferedVideoStreamUrl != null) { @@ -1122,13 +1174,14 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { if (checkInterrupted()) return; if (shouldResetAudioSource && prefferedAudioStream != null && prefferedAudioStreamUrl != null) { + final audioSource = LockCachingAudioSource( + prefferedAudioStreamUrl, + cacheFile: File(prefferedAudioStream.cachePath(item.id)), + tag: mediaItem, + onCacheDone: (cacheFile) async => await _onAudioCacheDone(item.id, cacheFile), + ); duration = await setSource( - LockCachingAudioSource( - prefferedAudioStreamUrl, - cacheFile: File(prefferedAudioStream.cachePath(item.id)), - tag: mediaItem, - onCacheDone: (cacheFile) async => await _onAudioCacheDone(item.id, cacheFile), - ), + audioSource, item: pi, startPlaying: startPlaying, videoOptions: videoOptions, @@ -1660,7 +1713,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { // ------- video ------- - Future setVideoSource({required AudioVideoSource source, String cacheKey = '', bool loopingAnimation = false, bool isFile = false}) async { + Future setVideoSource({required AudioVideoSource source, bool loopingAnimation = false, bool isFile = false}) async { if (isFile && source is UriSource) File.fromUri(source.uri).setLastAccessedTry(DateTime.now()); final videoOptions = VideoSourceOptions( source: source, diff --git a/lib/controller/json_to_history_parser.dart b/lib/controller/json_to_history_parser.dart index 3ea6b99d..13b6fc2a 100644 --- a/lib/controller/json_to_history_parser.dart +++ b/lib/controller/json_to_history_parser.dart @@ -392,7 +392,7 @@ class JsonToHistoryParser { final startTime = DateTime.now(); _notificationTimer?.cancel(); _notificationTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - NotificationService.inst.importHistoryNotification(parsedHistoryJson.value, totalJsonToParse.value, startTime); + NotificationService.importHistoryNotification(parsedHistoryJson.value, totalJsonToParse.value, startTime); }); final datesAdded = []; @@ -446,7 +446,7 @@ class JsonToHistoryParser { isParsing.value = false; _notificationTimer?.cancel(); - NotificationService.inst.doneImportingHistoryNotification(parsedHistoryJson.value, addedHistoryJsonToPlaylist.value); + NotificationService.doneImportingHistoryNotification(parsedHistoryJson.value, addedHistoryJsonToPlaylist.value); _latestMissingMap.value = allMissingEntries; _latestMissingMapAddedStatus.clear(); diff --git a/lib/controller/notification_controller.dart b/lib/controller/notification_controller.dart index 57afe557..d9618ed1 100644 --- a/lib/controller/notification_controller.dart +++ b/lib/controller/notification_controller.dart @@ -7,45 +7,35 @@ import 'package:namida/core/extensions.dart'; import 'package:namida/youtube/class/download_task_base.dart'; class NotificationService { - //Hanle displaying of notifications. - static final NotificationService _notificationService = NotificationService._internal(); static final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - factory NotificationService() => NotificationService._internal(); - - static NotificationService get inst => _notificationService; - - NotificationService._internal() { - init(); - } - static Future cancelAll() async { try { - await _flutterLocalNotificationsPlugin.cancelAll(); + return _flutterLocalNotificationsPlugin.cancelAll(); } catch (_) {} } - final _historyImportID = 1; + static const _historyImportID = 1; static const _historyImportPayload = 'history_import'; - final _historyImportChannelName = 'History Import'; - final _historyImportChannelDescription = 'Imports Tracks to History from a source'; + static const _historyImportChannelName = 'History Import'; + static const _historyImportChannelDescription = 'Imports Tracks to History from a source'; - final _youtubeDownloadID = 2; - final _youtubeDownloadPayload = 'youtube_download'; - final _youtubeDownloadChannelName = 'Downloads'; - final _youtubeDownloadChannelDescription = 'Downlaod content from youtube'; + static const _youtubeDownloadID = 2; + static const _youtubeDownloadPayload = 'youtube_download'; + static const _youtubeDownloadChannelName = 'Downloads'; + static const _youtubeDownloadChannelDescription = 'Downlaod content from youtube'; - static Future init() async { - await _flutterLocalNotificationsPlugin.initialize( + static Future init() { + return _flutterLocalNotificationsPlugin.initialize( const InitializationSettings( android: AndroidInitializationSettings('ic_stat_musicnote'), ), - onDidReceiveBackgroundNotificationResponse: (details) => _onDidReceiveLocalNotification(details), - onDidReceiveNotificationResponse: (details) => _onDidReceiveLocalNotification(details), + onDidReceiveBackgroundNotificationResponse: _onDidReceiveLocalNotification, + onDidReceiveNotificationResponse: _onDidReceiveLocalNotification, ); } - void mediaNotification({ + static void mediaNotification({ required String title, required String subText, required String subtitle, @@ -94,7 +84,7 @@ class NotificationService { ); } - void downloadYoutubeNotification({ + static void downloadYoutubeNotification({ required DownloadTaskFilename notificationID, required String title, required String Function(String progressText) subtitle, @@ -121,11 +111,11 @@ class NotificationService { ); } - Future removeDownloadingYoutubeNotification({required DownloadTaskFilename notificationID}) async { + static Future removeDownloadingYoutubeNotification({required DownloadTaskFilename notificationID}) async { await _flutterLocalNotificationsPlugin.cancel(_youtubeDownloadID, tag: notificationID.filename); } - void doneDownloadingYoutubeNotification({ + static void doneDownloadingYoutubeNotification({ required DownloadTaskFilename notificationID, required String videoTitle, required String subtitle, @@ -148,7 +138,7 @@ class NotificationService { ); } - void importHistoryNotification(int parsed, int total, DateTime displayTime) { + static void importHistoryNotification(int parsed, int total, DateTime displayTime) { _createProgressNotification( id: _historyImportID, progress: parsed, @@ -163,7 +153,7 @@ class NotificationService { ); } - void doneImportingHistoryNotification(int totalParsed, int totalAdded) { + static void doneImportingHistoryNotification(int totalParsed, int totalAdded) { _createNotification( id: _historyImportID, title: 'Done importing history', @@ -183,7 +173,7 @@ class NotificationService { } } - void _createNotification({ + static void _createNotification({ required int id, required String title, required String body, @@ -225,7 +215,7 @@ class NotificationService { ); } - void _createProgressNotification({ + static void _createProgressNotification({ required int id, required int progress, required int maxProgress, diff --git a/lib/controller/player_controller.dart b/lib/controller/player_controller.dart index a643fad4..5bf45f32 100644 --- a/lib/controller/player_controller.dart +++ b/lib/controller/player_controller.dart @@ -605,8 +605,8 @@ class Player { return _audioHandler.tryGenerateWaveform(video); } - Future setVideo({required AudioVideoSource source, String cacheKey = '', bool loopingAnimation = false, required bool isFile}) async { - await _audioHandler.setVideoSource(source: source, cacheKey: cacheKey, loopingAnimation: loopingAnimation, isFile: isFile); + Future setVideo({required AudioVideoSource source, bool loopingAnimation = false, required bool isFile}) async { + await _audioHandler.setVideoSource(source: source, loopingAnimation: loopingAnimation, isFile: isFile); } Future disposeVideo() async { diff --git a/lib/controller/search_sort_controller.dart b/lib/controller/search_sort_controller.dart index 6e53f9ab..1f05bf25 100644 --- a/lib/controller/search_sort_controller.dart +++ b/lib/controller/search_sort_controller.dart @@ -871,7 +871,7 @@ class SearchSortController { sortThis((e) => e.value[0].artistsList.join().toLowerCase()); break; case GroupSortType.genresList: - sortThis((e) => e.value[0].genresList.join().toLowerCase()); + sortThis((e) => e.key.toLowerCase()); break; case GroupSortType.composer: sortThis((e) => e.value.composer.toLowerCase()); diff --git a/lib/main.dart b/lib/main.dart index bfd35b29..6587d819 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -202,6 +202,7 @@ void mainInitialization() async { NamidaNavigator.inst.setDefaultSystemUIOverlayStyle(); ScrollSearchController.inst.initialize(); + NotificationService.init(); NotificationService.cancelAll(); FlutterVolumeController.updateShowSystemUI(false); CurrentColor.inst.initialize(); @@ -297,7 +298,7 @@ Future _initializeIntenties() async { final linkRaw = files.first.value; if (linkRaw != null) { final link = linkRaw.replaceAll(r'\', ''); - if (link.startsWith('app://com.msob7y.namida.patreonauth')) { + if (link.startsWith('app://patreonauth.msob7y.namida')) { final link = linkRaw.replaceAll(r'\', ''); YoutubeAccountController.membership.redirectUrlCompleter?.completeIfWasnt(link); return; diff --git a/lib/ui/dialogs/general_popup_dialog.dart b/lib/ui/dialogs/general_popup_dialog.dart index 51b3fcd3..bfb71d67 100644 --- a/lib/ui/dialogs/general_popup_dialog.dart +++ b/lib/ui/dialogs/general_popup_dialog.dart @@ -165,57 +165,6 @@ Future showGeneralPopupDialog( void cancelSkipTimer() => Player.inst.cancelPlayErrorSkipTimer(); - void setMoodsOrTags(List initialMoods, void Function(List moodsFinal) saveFunction, {bool isTags = false}) async { - final controller = TextEditingController(); - final currentMoods = initialMoods.join(', '); - controller.text = currentMoods; - - final title = isTags ? lang.SET_TAGS : lang.SET_MOODS; - final subtitle = lang.SET_MOODS_SUBTITLE; - await openDialog( - onDisposing: () { - controller.dispose(); - }, - (theme) => CustomBlurryDialog( - title: title, - actions: [ - const CancelButton(), - NamidaButton( - text: lang.SAVE, - onPressed: () async { - List moodsPre = controller.text.split(','); - List moodsFinal = []; - moodsPre.loop((m) { - if (!m.contains(',') && m != ' ' && m.isNotEmpty) { - moodsFinal.add(m.trimAll()); - } - }); - - saveFunction(moodsFinal.uniqued()); - - NamidaNavigator.inst.closeDialog(); - }, - ), - ], - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - subtitle, - style: theme.textTheme.displaySmall, - ), - const SizedBox(height: 20.0), - CustomTagTextField( - controller: controller, - hintText: currentMoods.overflow, - labelText: title, - ), - ], - ), - ), - ); - } - final stats = tracks.firstOrNull?.stats.obs; List splitByCommaList(String listText) { diff --git a/lib/ui/widgets/custom_widgets.dart b/lib/ui/widgets/custom_widgets.dart index e91f6359..2d2fb992 100644 --- a/lib/ui/widgets/custom_widgets.dart +++ b/lib/ui/widgets/custom_widgets.dart @@ -217,6 +217,7 @@ class CustomListTile extends StatelessWidget { final TextStyle? titleStyle; final double borderR; final Color? bgColor; + final double verticalPadding; const CustomListTile({ super.key, @@ -237,6 +238,7 @@ class CustomListTile extends StatelessWidget { this.titleStyle, this.borderR = 20.0, this.bgColor, + this.verticalPadding = 0.0, }); @override @@ -253,7 +255,7 @@ class CustomListTile extends StatelessWidget { ), visualDensity: visualDensity, onTap: onTap, - contentPadding: const EdgeInsets.symmetric(horizontal: 16.0), + contentPadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: verticalPadding), minVerticalPadding: 8.0, leading: icon != null ? SizedBox( @@ -711,7 +713,7 @@ class SmallListTile extends StatelessWidget { size: 18.0, ), ), - visualDensity: visualDensity ?? (compact ? const VisualDensity(horizontal: -2.0, vertical: -2.0) : const VisualDensity(horizontal: -1.0, vertical: -1.0)), + visualDensity: visualDensity ?? (compact ? const VisualDensity(horizontal: -2.2, vertical: -2.2) : const VisualDensity(horizontal: -1.2, vertical: -1.2)), title: Text( title, style: context.textTheme.displayMedium?.copyWith( diff --git a/lib/youtube/controller/youtube_controller.dart b/lib/youtube/controller/youtube_controller.dart index e6183a37..8124a944 100644 --- a/lib/youtube/controller/youtube_controller.dart +++ b/lib/youtube/controller/youtube_controller.dart @@ -151,13 +151,17 @@ class YoutubeController { } } - NotificationService.inst.removeDownloadingYoutubeNotification(notificationID: oldFilename); + NotificationService.removeDownloadingYoutubeNotification(notificationID: oldFilename); YTOnGoingFinishedDownloads.inst.refreshList(); await _writeTaskGroupToStorage(groupName: groupName); } + AudioStream? getPreferredAudioStream(List audiostreams) { + return audiostreams.firstWhereEff((e) => !e.isWebm && e.audioTrack?.langCode == 'en') ?? audiostreams.firstWhereEff((e) => !e.isWebm) ?? audiostreams.firstOrNull; + } + VideoStream? getPreferredStreamQuality(List streams, {List qualities = const [], bool preferIncludeWebm = false}) { if (streams.isEmpty) return null; final preferredQualities = (qualities.isNotEmpty ? qualities : settings.youtubeVideoQualities.value).map((element) => element.settingLabeltoVideoLabel()); @@ -187,11 +191,12 @@ class YoutubeController { required String? Function(DownloadTaskVideoId videoId) titleCallback, required File? Function(DownloadTaskVideoId videoId) imageCallback, }) { + List? pendingFnsAfterLoop; final downloadingText = isAudio ? "Audio" : "Video"; - for (final bigEntry in downloadingMap.entries.toList()) { + for (final bigEntry in downloadingMap.entries) { final map = bigEntry.value.value; final videoId = bigEntry.key; - for (final entry in map.entries.toList()) { + for (final entry in map.entries) { final filename = entry.key; final progressInfo = (isAudio ? downloadsAudioProgressMap : downloadsVideoProgressMap).value[videoId]?.value[filename]; if (progressInfo == null) continue; @@ -202,7 +207,12 @@ class YoutubeController { if (percentage >= 1 || percentage.isNaN || percentage.isInfinite) continue; final isRunning = entry.value; - if (isRunning == false) downloadingMap[videoId]?.remove(filename); // to ensure next iteration wont post pause again --^ + if (isRunning == false) { + pendingFnsAfterLoop ??= []; + pendingFnsAfterLoop.add(() { + downloadingMap[videoId]?.remove(filename); // to ensure next iteration wont post pause again --^ + }); + } final title = titleCallback(videoId) ?? videoId; final speedB = speedInBytes(filename, progressInfo.progress); @@ -213,7 +223,7 @@ class YoutubeController { currentSpeedsInByte.value[videoId]![filename] = speedB; var keyword = isRunning ? 'Downloading' : 'Paused'; - NotificationService.inst.downloadYoutubeNotification( + NotificationService.downloadYoutubeNotification( notificationID: entry.key, title: "$keyword $downloadingText: $title", progress: p, @@ -225,6 +235,10 @@ class YoutubeController { ); } } + if (pendingFnsAfterLoop != null) { + pendingFnsAfterLoop.loop((fn) => fn()); + pendingFnsAfterLoop = null; + } } void _doneDownloadingNotification({ @@ -237,7 +251,7 @@ class YoutubeController { }) { if (downloadedFile == null) { if (!canceledByUser) { - NotificationService.inst.doneDownloadingYoutubeNotification( + NotificationService.doneDownloadingYoutubeNotification( notificationID: nameIdentifier, videoTitle: videoTitle, subtitle: 'Download Failed', @@ -247,19 +261,18 @@ class YoutubeController { } } else { final size = downloadedFile.fileSizeFormatted(); - NotificationService.inst.doneDownloadingYoutubeNotification( + NotificationService.doneDownloadingYoutubeNotification( notificationID: nameIdentifier, videoTitle: downloadedFile.path.getFilenameWOExt, subtitle: size == null ? '' : 'Downloaded: $size', imagePath: _notificationData.imageCallback(videoId)?.path, failed: false, ); + // -- remove progress only if succeeded. + downloadsVideoProgressMap[videoId]?.remove(filename); + downloadsAudioProgressMap[videoId]?.remove(filename); } _tryCancelDownloadNotificationTimer(); - - // this removes progress when pausing. - // downloadsVideoProgressMap[videoId]?.remove(filename); - // downloadsAudioProgressMap[videoId]?.remove(filename); } Timer? _downloadNotificationTimer; @@ -909,6 +922,8 @@ class YoutubeController { // --------- Downloading Choosen Audio. if (skipAudio == false && audioStream != null) { + downloadsVideoProgressMap[id]?.remove(finalFilenameWrapper); // remove video progress so that audio progress is shown + final filecache = audioStream.getCachedFile(id.videoId); if (useCachedVersionsIfAvailable && filecache != null && await fileSizeQualified(file: filecache, targetSize: audioStream.sizeInBytes)) { audioFile = filecache; diff --git a/lib/youtube/pages/user/youtube_account_manage_page.dart b/lib/youtube/pages/user/youtube_account_manage_page.dart index fd0be309..aca71d91 100644 --- a/lib/youtube/pages/user/youtube_account_manage_page.dart +++ b/lib/youtube/pages/user/youtube_account_manage_page.dart @@ -149,6 +149,7 @@ class YoutubeAccountManagePage extends StatelessWidget with NamidaRouteWidget { final acc = signedInAccounts[index]; final active = currentChannel?.id == acc.id; return CustomListTile( + verticalPadding: acc.handler.isEmpty ? 6.0 : 0.0, title: acc.title, subtitle: acc.handler, bgColor: active ? accountColorActive : accountColorNonActive, diff --git a/pubspec.yaml b/pubspec.yaml index 01d7d36c..82f3a18e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: namida description: A Beautiful and Feature-rich Music Player, With YouTube & Video Support Built in Flutter publish_to: "none" -version: 3.6.7-beta+240725238 +version: 3.6.9-beta+240727198 environment: sdk: ">=3.4.0 <4.0.0" @@ -46,7 +46,7 @@ dependencies: share_plus: ^9.0.0 calendar_date_picker2: ^1.0.2 wakelock_plus: ^1.1.1 - flutter_local_notifications: ^17.1.2 + flutter_local_notifications: ^17.2.1+2 flutter_sharing_intent: git: url: https://github.com/MSOB7YY/flutter_sharing_intent