From 38b1e7e5a8b6618336cf842085f55406e959a3b0 Mon Sep 17 00:00:00 2001 From: Sheikh Haziq Date: Wed, 15 Jan 2025 22:44:03 +0530 Subject: [PATCH] made some bug fixes in song loading --- lib/app_config.dart | 2 +- lib/main.dart | 1 + lib/screens/home_screen/home_screen.dart | 73 ++++++------ lib/screens/main_screen/player_screen.dart | 58 ++++++---- lib/services/custom_audio_stream.dart | 126 ++++++++++++--------- lib/services/media_player.dart | 35 +++++- lib/services/stream_client.dart | 2 +- lib/services/yt_audio_stream.dart | 52 +++++++++ lib/utils/bottom_modals.dart | 5 +- lib/utils/router.dart | 1 + pubspec.lock | 8 +- pubspec.yaml | 9 +- 12 files changed, 241 insertions(+), 131 deletions(-) create mode 100644 lib/services/yt_audio_stream.dart diff --git a/lib/app_config.dart b/lib/app_config.dart index 22d3cae..a06b22d 100644 --- a/lib/app_config.dart +++ b/lib/app_config.dart @@ -1,4 +1,4 @@ -AppConfig appConfig = AppConfig(version: 36, codeName: '2.0.11'); +AppConfig appConfig = AppConfig(version: 37, codeName: '2.0.12'); class AppConfig { int version; diff --git a/lib/main.dart b/lib/main.dart index b9e9809..f3a0ce7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:io'; import 'package:dynamic_color/dynamic_color.dart'; diff --git a/lib/screens/home_screen/home_screen.dart b/lib/screens/home_screen/home_screen.dart index a8bdab5..b66fc3c 100644 --- a/lib/screens/home_screen/home_screen.dart +++ b/lib/screens/home_screen/home_screen.dart @@ -131,45 +131,42 @@ class _HomeScreenState extends State { return AdaptiveScaffold( appBar: PreferredSize( preferredSize: const AdaptiveAppBar().preferredSize, - child: AdaptiveAppBar( - automaticallyImplyLeading: false, - title: Material( - color: Colors.transparent, - child: LayoutBuilder( - builder: (context, constraints) { - return Row( - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: constraints.maxWidth > 400 - ? (400) - : constraints.maxWidth), - child: AdaptiveTextField( - onTap: () => context.go('/search'), - readOnly: true, - keyboardType: TextInputType.text, - maxLines: 1, - autofocus: false, - textInputAction: TextInputAction.search, - fillColor: Platform.isWindows - ? null - : Colors.grey.withOpacity(0.3), - contentPadding: const EdgeInsets.symmetric( - vertical: 2, horizontal: 8), - borderRadius: - BorderRadius.circular(Platform.isWindows ? 4.0 : 35), - hintText: S.of(context).Search_Gyawun, - prefix: Icon(AdaptiveIcons.search), - ), - ), - ], - ); - } - ), - ), - centerTitle: false, + child: AdaptiveAppBar( + automaticallyImplyLeading: false, + title: Material( + color: Colors.transparent, + child: LayoutBuilder(builder: (context, constraints) { + return Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: constraints.maxWidth > 400 + ? (400) + : constraints.maxWidth), + child: AdaptiveTextField( + onTap: () => context.go('/search'), + readOnly: true, + keyboardType: TextInputType.text, + maxLines: 1, + autofocus: false, + textInputAction: TextInputAction.search, + fillColor: Platform.isWindows + ? null + : Colors.grey.withOpacity(0.3), + contentPadding: const EdgeInsets.symmetric( + vertical: 2, horizontal: 8), + borderRadius: + BorderRadius.circular(Platform.isWindows ? 4.0 : 35), + hintText: S.of(context).Search_Gyawun, + prefix: Icon(AdaptiveIcons.search), + ), + ), + ], + ); + }), ), - + centerTitle: false, + ), ), body: initialLoading ? const Center(child: AdaptiveProgressRing()) diff --git a/lib/screens/main_screen/player_screen.dart b/lib/screens/main_screen/player_screen.dart index eb09d67..4db977f 100644 --- a/lib/screens/main_screen/player_screen.dart +++ b/lib/screens/main_screen/player_screen.dart @@ -12,7 +12,6 @@ import 'package:gyawun/utils/extensions.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:just_audio/just_audio.dart'; import 'package:just_audio_background/just_audio_background.dart'; -import 'package:palette_generator/palette_generator.dart'; import 'package:provider/provider.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; import 'package:text_scroll/text_scroll.dart'; @@ -113,9 +112,8 @@ class _PlayerScreenState extends State { Future getColor(String? image, bool isDark) async { if (image == null) return Theme.of(context).scaffoldBackgroundColor; - PaletteGenerator paletteGenerator = - await PaletteGenerator.fromImageProvider( - CachedNetworkImageProvider( + final c = await ColorScheme.fromImageProvider( + provider: CachedNetworkImageProvider( image, errorListener: (p0) { if (mounted) { @@ -128,24 +126,40 @@ class _PlayerScreenState extends State { }, ), ); + return c.primary; + // PaletteGenerator paletteGenerator = + // await PaletteGenerator.fromImageProvider( + // CachedNetworkImageProvider( + // image, + // errorListener: (p0) { + // if (mounted) { + // setState(() { + // image = getEnhancedImage(image!, + // dp: MediaQuery.of(context).devicePixelRatio, + // quality: 'medium'); + // }); + // } + // }, + // ), + // ); - if (mounted) { - if (isDark) { - return paletteGenerator.darkVibrantColor?.color ?? - paletteGenerator.dominantColor?.color ?? - paletteGenerator.darkMutedColor?.color ?? - paletteGenerator.lightVibrantColor?.color ?? - paletteGenerator.lightMutedColor?.color; - } else { - return paletteGenerator.lightMutedColor?.color ?? - paletteGenerator.darkVibrantColor?.color ?? - paletteGenerator.dominantColor?.color ?? - paletteGenerator.darkMutedColor?.color ?? - paletteGenerator.lightVibrantColor?.color; - } - } else { - return Colors.transparent; - } + // if (mounted) { + // if (isDark) { + // return paletteGenerator.darkVibrantColor?.color ?? + // paletteGenerator.dominantColor?.color ?? + // paletteGenerator.darkMutedColor?.color ?? + // paletteGenerator.lightVibrantColor?.color ?? + // paletteGenerator.lightMutedColor?.color; + // } else { + // return paletteGenerator.lightMutedColor?.color ?? + // paletteGenerator.darkVibrantColor?.color ?? + // paletteGenerator.dominantColor?.color ?? + // paletteGenerator.darkMutedColor?.color ?? + // paletteGenerator.lightVibrantColor?.color; + // } + // } else { + // return Colors.transparent; + // } } MaterialColor primaryWhite = const MaterialColor( @@ -675,7 +689,7 @@ class NameAndControls extends StatelessWidget { item['status'] == 'DOWNLOADING') { return AdaptiveProgressRing( value: item['status'] == 'DOWNLOADING' - ? item['progress']/100 + ? item['progress'] / 100 : null, color: Colors.white, backgroundColor: Colors.black, diff --git a/lib/services/custom_audio_stream.dart b/lib/services/custom_audio_stream.dart index a5d3759..93d1836 100644 --- a/lib/services/custom_audio_stream.dart +++ b/lib/services/custom_audio_stream.dart @@ -1,55 +1,71 @@ -import 'dart:async'; -import 'package:gyawun/services/stream_client.dart'; -import 'package:just_audio/just_audio.dart'; -import 'package:youtube_explode_dart/youtube_explode_dart.dart'; - -class CustomAudioStream extends StreamAudioSource { - late YoutubeExplode ytExplode; - AudioOnlyStreamInfo? streamInfo; - String videoId; - String quality; - - CustomAudioStream(this.videoId, this.quality, {super.tag}) { - ytExplode = YoutubeExplode(); - } - - Future _loadStreamInfo() async { - StreamManifest manifest = await ytExplode.videos.streamsClient - .getManifest(videoId, requireWatchPage: false); - - List streamInfos = manifest.audioOnly - .sortByBitrate() - .reversed - .where((stream) => stream.container == StreamContainer.mp4) - .toList(); - - int qualityIndex = quality == 'low' ? 0 : streamInfos.length - 1; - streamInfo = streamInfos[qualityIndex]; - } - - @override - Future request([int? start, int? end]) async { - if (streamInfo == null) { - await _loadStreamInfo(); - } - - start ??= 0; - int size = streamInfo!.bitrate.bitsPerSecond * 80; - end ??= (streamInfo!.isThrottled - ? (start + size) - : streamInfo!.size.totalBytes); - if (end > streamInfo!.size.totalBytes) { - end = streamInfo!.size.totalBytes; - } - final response = - AudioStreamClient().getAudioStream(streamInfo, start: start, end: end); - - return StreamAudioResponse( - sourceLength: streamInfo!.size.totalBytes, - contentLength: end - start, - offset: start, - stream: response, - contentType: streamInfo!.codec.type, - ); - } -} +// import 'dart:async'; +// import 'package:gyawun/services/stream_client.dart'; +// import 'package:just_audio/just_audio.dart'; +// import 'package:youtube_explode_dart/youtube_explode_dart.dart'; + +// mixin HlsStreamInfo on StreamInfo { +// /// The tag of the audio stream related to this stream. +// int? get audioItag => null; +// } + +// class CustomAudioStream extends StreamAudioSource { +// late YoutubeExplode ytExplode; +// StreamInfo? streamInfo; +// String videoId; +// String quality; + +// CustomAudioStream(this.videoId, this.quality, {super.tag}) { +// ytExplode = YoutubeExplode(); +// } + +// Future _loadStreamInfo() async { +// StreamManifest manifest = +// await ytExplode.videos.streamsClient.getManifest(videoId); + +// List streamInfos = manifest.audioOnly +// .sortByBitrate() +// .reversed +// .where((stream) => stream.container == StreamContainer.mp4) +// .toList(); + +// int qualityIndex = quality == 'low' ? 0 : streamInfos.length - 1; +// streamInfo = streamInfos[qualityIndex]; +// if (streamInfo!.fragments.isNotEmpty) { +// print("Using DASH Fragmented Stream"); +// } else if (streamInfo is HlsStreamInfo) { +// print("Using HLS Stream"); +// } else { +// print("Using Normal Stream"); +// } +// print(streamInfo!.url.toString()); +// } + +// @override +// Future request([int? start, int? end]) async { +// if (streamInfo == null) { +// await _loadStreamInfo(); +// } + +// start ??= 0; + +// int size = 10379935; +// end = start + size.clamp(0, streamInfo!.size.totalBytes - start); +// // if (!streamInfo!.isThrottled) { + +// // } +// // end ??= streamInfo!.size.totalBytes; +// if (end > streamInfo!.size.totalBytes) { +// end = streamInfo!.size.totalBytes; +// } +// print('$start-$end'); +// final response = ytExplode.videos.streams.get(streamInfo!, start, end); + +// return StreamAudioResponse( +// sourceLength: streamInfo!.size.totalBytes, +// contentLength: null, +// offset: null, +// stream: response, +// contentType: streamInfo!.codec.type, +// ); +// } +// } diff --git a/lib/services/media_player.dart b/lib/services/media_player.dart index b09dfa0..7b72034 100644 --- a/lib/services/media_player.dart +++ b/lib/services/media_player.dart @@ -6,12 +6,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:get_it/get_it.dart'; +import 'package:gyawun/services/yt_audio_stream.dart'; import 'package:just_audio/just_audio.dart'; import 'package:just_audio_background/just_audio_background.dart'; import '../utils/add_history.dart'; import '../ytmusic/ytmusic.dart'; -import 'custom_audio_stream.dart'; import 'settings_manager.dart'; class MediaPlayer extends ChangeNotifier { @@ -278,15 +278,42 @@ class MediaPlayer extends ChangeNotifier { if (isDownloaded) { audioSource = AudioSource.file(song['path'], tag: tag); } else { - audioSource = CustomAudioStream( - song['videoId'], - GetIt.I().streamingQuality.name.toLowerCase(), + // audioSource = AudioSource.uri( + // Uri.parse( + // 'https://invidious.nerdvpn.de/latest_version?id=${song['videoId']}'), + // tag: tag, + // ); + + audioSource = YouTubeAudioSource( + videoId: song['videoId'], + quality: GetIt.I().streamingQuality.name.toLowerCase(), tag: tag, ); } return audioSource; } + // void download() async { + // List qualVidOptions = ['480p', '720p', '1080p', '2K', '4K']; + // List qualAudOptions = ['64k', '128k', '192k', '256k', '320k']; + + // String command = + // 'yt-dlp -f \'bestvideo[height<=${qualVid.replaceAll('p', '')}]' + // '+bestaudio/best[height<=${qualVid.replaceAll('p', '')}]\' $ytUrl'; + // if (dlAud) command += ' -x --audio-format mp3 --audio-quality ${qualAud}'; + // if (dlThumb) command += ' --write-thumbnail'; + // if (timeStart != null && timeEnd != null) { + // command += + // ' --postprocessor-args "-ss ${timeStart!.format(context)} -to ${timeEnd!.format(context)}"'; + // } + // Shell shell = Shell(); + // if (kDebugMode) { + // print("Running command: ==================================== "); + // print(command); + // } + // await shell.run(command); + // } + Future playSong(Map song, {bool autoFetch = true}) async { if (song['videoId'] != null) { diff --git a/lib/services/stream_client.dart b/lib/services/stream_client.dart index d6cd7d4..e3d1c7c 100644 --- a/lib/services/stream_client.dart +++ b/lib/services/stream_client.dart @@ -37,7 +37,7 @@ class AudioStreamClient { }) async* { var url = streamInfo.url; var bytesCount = start; - while (bytesCount != streamInfo.size.totalBytes) { + while (bytesCount != end) { try { final response = await retry(this, () async { final from = bytesCount; diff --git a/lib/services/yt_audio_stream.dart b/lib/services/yt_audio_stream.dart new file mode 100644 index 0000000..d131020 --- /dev/null +++ b/lib/services/yt_audio_stream.dart @@ -0,0 +1,52 @@ +import 'dart:async'; + +import 'package:just_audio/just_audio.dart'; +import 'package:youtube_explode_dart/youtube_explode_dart.dart'; + +class YouTubeAudioSource extends StreamAudioSource { + final String videoId; + final String quality; // 'high' or 'low' + final YoutubeExplode ytExplode; + + YouTubeAudioSource({ + required this.videoId, + required this.quality, + super.tag, + }) : ytExplode = YoutubeExplode(); + + @override + Future request([int? start, int? end]) async { + try { + final manifest = await ytExplode.videos.streams.getManifest(videoId, + requireWatchPage: false, ytClients: [YoutubeApiClient.android]); + final supportedStreams = manifest.audioOnly.sortByBitrate(); + + final audioStream = quality == 'high' + ? supportedStreams.lastOrNull + : supportedStreams.firstOrNull; + + if (audioStream == null) { + throw Exception('No audio stream available for this video.'); + } + + start ??= 0; + end = (audioStream.isThrottled + ? (end ?? (start + 10379935)) + : audioStream.size.totalBytes); + if (end > audioStream.size.totalBytes) { + end = audioStream.size.totalBytes; + } + + final stream = ytExplode.videos.streams.get(audioStream, start, end); + return StreamAudioResponse( + sourceLength: audioStream.size.totalBytes, + contentLength: end - start, + offset: start, + stream: stream, + contentType: audioStream.codec.mimeType, + ); + } catch (e) { + throw Exception('Failed to load audio: $e'); + } + } +} diff --git a/lib/utils/bottom_modals.dart b/lib/utils/bottom_modals.dart index 8a65cf0..a308a8a 100644 --- a/lib/utils/bottom_modals.dart +++ b/lib/utils/bottom_modals.dart @@ -775,7 +775,10 @@ _updateDialog(BuildContext context, UpdateInfo? updateInfo) { onPressed: () { Navigator.pop(context); if (updateInfo != null) { - launchUrl(Uri.parse(updateInfo.downloadUrl), + String platform = Platform.isAndroid ? 'android' : 'windows'; + launchUrl( + Uri.parse( + 'https://gyawunmusic.vercel.app/api/download?action=download&platform=$platform&url=${updateInfo.downloadUrl}'), mode: LaunchMode.externalApplication); } }, diff --git a/lib/utils/router.dart b/lib/utils/router.dart index d2d433c..9f4ec75 100644 --- a/lib/utils/router.dart +++ b/lib/utils/router.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../screens/home_screen/chip_screen.dart'; diff --git a/pubspec.lock b/pubspec.lock index 224b325..936936d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1366,11 +1366,9 @@ packages: youtube_explode_dart: dependency: "direct main" description: - path: "." - ref: e519db6 - resolved-ref: e519db65ad0b0a40b12f69285932f9db509da3cf - url: "https://github.com/Hexer10/youtube_explode_dart.git" - source: git + path: "../youtube_explode_dart" + relative: true + source: path version: "2.3.7" sdks: dart: ">=3.5.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 88f6a0c..8a67e20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: gyawun description: "A new Flutter project." publish_to: 'none' -version: 2.0.11+36 +version: 2.0.12+37 environment: sdk: '>=3.4.1 <4.0.0' @@ -19,9 +19,10 @@ dependencies: hive: ^2.2.3 hive_flutter: ^1.1.0 youtube_explode_dart: - git: - url: https://github.com/Hexer10/youtube_explode_dart.git - ref: e519db6 + path: ../youtube_explode_dart + # git: + # url: https://github.com/Hexer10/youtube_explode_dart.git + # ref: e519db6 expandable_page_view: ^1.0.17 get_it: ^7.7.0 cached_network_image: ^3.3.1