From a8d2210d524c5cc7cc715258290936ded926e09a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 16 Oct 2024 21:12:58 +0600 Subject: [PATCH] feat: add invidious audio source with automatic track switch even on server playback endpoint --- assets/invidious.jpg | Bin 0 -> 100201 bytes lib/collections/assets.gen.dart | 2 + lib/l10n/app_en.arb | 1 + lib/models/connect/connect.g.dart | 4 +- lib/models/database/tables/preferences.dart | 3 +- lib/modules/player/sibling_tracks_sheet.dart | 12 + .../getting_started/sections/playback.dart | 9 +- lib/pages/settings/sections/playback.dart | 3 +- lib/provider/server/routes/playback.dart | 43 ++- lib/provider/server/sourced_track.dart | 5 +- .../sourced_track/models/source_info.g.dart | 2 +- .../sourced_track/models/video_info.dart | 21 ++ lib/services/sourced_track/sourced_track.dart | 58 +++- .../sourced_track/sources/invidious.dart | 263 ++++++++++++++++++ pubspec.lock | 33 ++- pubspec.yaml | 4 + untranslated_messages.json | 106 ++++++- 17 files changed, 539 insertions(+), 30 deletions(-) create mode 100644 assets/invidious.jpg create mode 100644 lib/services/sourced_track/sources/invidious.dart diff --git a/assets/invidious.jpg b/assets/invidious.jpg new file mode 100644 index 0000000000000000000000000000000000000000..12c5f10762bb28f352c190e2afb29225d582b56b GIT binary patch literal 100201 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 Qdf=@G{{Q!Y`f|_z0f&V`6aWAK literal 0 HcmV?d00001 diff --git a/lib/collections/assets.gen.dart b/lib/collections/assets.gen.dart index cff5b74f..f10242e8 100644 --- a/lib/collections/assets.gen.dart +++ b/lib/collections/assets.gen.dart @@ -49,6 +49,7 @@ class Assets { AssetGenImage('assets/bengali-patterns-bg.jpg'); static const AssetGenImage branding = AssetGenImage('assets/branding.png'); static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png'); + static const AssetGenImage invidious = AssetGenImage('assets/invidious.jpg'); static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png'); static const AssetGenImage likedTracks = AssetGenImage('assets/liked-tracks.jpg'); @@ -95,6 +96,7 @@ class Assets { bengaliPatternsBg, branding, emptyBox, + invidious, jiosaavn, likedTracks, placeholder, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c63f8543..6bb5b9bc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -307,6 +307,7 @@ "youtube_source_description": "Recommended and works best.", "piped_source_description": "Feeling free? Same as YouTube but a lot free.", "jiosaavn_source_description": "Best for South Asian region.", + "invidious_source_description": "Similar to Piped but with higher availability.", "highest_quality": "Highest Quality: {quality}", "select_audio_source": "Select Audio Source", "endless_playback_description": "Automatically append new songs\nto the end of the queue", diff --git a/lib/models/connect/connect.g.dart b/lib/models/connect/connect.g.dart index f297024b..10f46c65 100644 --- a/lib/models/connect/connect.g.dart +++ b/lib/models/connect/connect.g.dart @@ -16,7 +16,7 @@ _$WebSocketLoadEventDataPlaylistImpl ? null : PlaylistSimple.fromJson( Map.from(json['collection'] as Map)), - initialIndex: json['initialIndex'] as int?, + initialIndex: (json['initialIndex'] as num?)?.toInt(), $type: json['runtimeType'] as String?, ); @@ -39,7 +39,7 @@ _$WebSocketLoadEventDataAlbumImpl _$$WebSocketLoadEventDataAlbumImplFromJson( ? null : AlbumSimple.fromJson( Map.from(json['collection'] as Map)), - initialIndex: json['initialIndex'] as int?, + initialIndex: (json['initialIndex'] as num?)?.toInt(), $type: json['runtimeType'] as String?, ); diff --git a/lib/models/database/tables/preferences.dart b/lib/models/database/tables/preferences.dart index ae4ec1e8..bca67974 100644 --- a/lib/models/database/tables/preferences.dart +++ b/lib/models/database/tables/preferences.dart @@ -14,7 +14,8 @@ enum CloseBehavior { enum AudioSource { youtube, piped, - jiosaavn; + jiosaavn, + invidious; String get label => name[0].toUpperCase() + name.substring(1); } diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index b58a5894..3a31d88e 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -23,6 +23,7 @@ import 'package:spotube/provider/user_preferences/user_preferences_provider.dart import 'package:spotube/services/sourced_track/models/source_info.dart'; import 'package:spotube/services/sourced_track/models/video_info.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; +import 'package:spotube/services/sourced_track/sources/invidious.dart'; import 'package:spotube/services/sourced_track/sources/jiosaavn.dart'; import 'package:spotube/services/sourced_track/sources/piped.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; @@ -42,6 +43,17 @@ final sourceInfoToIconMap = { ), ), PipedSourceInfo: const Icon(SpotubeIcons.piped), + InvidiousSourceInfo: Container( + height: 18, + width: 18, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(90), + image: DecorationImage( + image: Assets.invidious.provider(), + fit: BoxFit.cover, + ), + ), + ), }; class SiblingTracksSheet extends HookConsumerWidget { diff --git a/lib/pages/getting_started/sections/playback.dart b/lib/pages/getting_started/sections/playback.dart index e7087afd..dbf0bda2 100644 --- a/lib/pages/getting_started/sections/playback.dart +++ b/lib/pages/getting_started/sections/playback.dart @@ -17,6 +17,10 @@ final audioSourceToIconMap = { size: 30, ), AudioSource.piped: const Icon(SpotubeIcons.piped, size: 30), + AudioSource.invidious: ClipRRect( + borderRadius: BorderRadius.circular(48), + child: Assets.invidious.image(width: 48, height: 48), + ), AudioSource.jiosaavn: Assets.jiosaavn.image(width: 48, height: 48), }; @@ -45,6 +49,7 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { AudioSource.jiosaavn: "${context.l10n.jiosaavn_source_description}\n" "${context.l10n.highest_quality("320kbps mp")}", + AudioSource.invidious: context.l10n.invidious_source_description, }, []); @@ -104,7 +109,9 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget { title: Align( alignment: switch (preferences.audioSource) { AudioSource.youtube => Alignment.centerLeft, - AudioSource.piped => Alignment.center, + AudioSource.piped || + AudioSource.invidious => + Alignment.center, AudioSource.jiosaavn => Alignment.centerRight, }, child: Text( diff --git a/lib/pages/settings/sections/playback.dart b/lib/pages/settings/sections/playback.dart index 6273c557..771d412d 100644 --- a/lib/pages/settings/sections/playback.dart +++ b/lib/pages/settings/sections/playback.dart @@ -159,7 +159,8 @@ class SettingsPlaybackSection extends HookConsumerWidget { duration: const Duration(milliseconds: 300), child: preferences.searchMode == SearchMode.youtube && (preferences.audioSource == AudioSource.piped || - preferences.audioSource == AudioSource.youtube) + preferences.audioSource == AudioSource.youtube || + preferences.audioSource == AudioSource.invidious) ? SwitchListTile( secondary: const Icon(SpotubeIcons.skip), title: Text(context.l10n.skip_non_music), diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index 30322a6f..2c5aa380 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -20,6 +20,17 @@ class ServerPlaybackRoutes { /// @get('/stream/') Future getStreamTrackId(Request request, String trackId) async { + final options = Options( + headers: { + ...request.headers, + "User-Agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + "Cache-Control": "max-age=0", + "Connection": "keep-alive", + }, + responseType: ResponseType.stream, + validateStatus: (status) => status! < 400, + ); try { final track = playlist.tracks.firstWhere((element) => element.id == trackId); @@ -30,22 +41,32 @@ class ServerPlaybackRoutes { : await ref.read(sourcedTrackProvider(SpotubeMedia(track)).future); ref.read(activeSourcedTrackProvider.notifier).update(sourcedTrack); - - final res = await dio.get( + final res = await dio + .get( sourcedTrack!.url, - options: Options( + options: options.copyWith( headers: { - ...request.headers, - "User-Agent": - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", + ...options.headers!, "host": Uri.parse(sourcedTrack.url).host, - "Cache-Control": "max-age=0", - "Connection": "keep-alive", }, - responseType: ResponseType.stream, - validateStatus: (status) => status! < 500, ), - ); + ) + .catchError((e, stack) async { + final sourcedTrack = await ref.read( + sourcedTrackProvider(SpotubeMedia(track, extras: {"switch": true})) + .future, + ); + + return await dio.get( + sourcedTrack!.url, + options: options.copyWith( + headers: { + ...options.headers!, + "host": Uri.parse(sourcedTrack.url).host, + }, + ), + ); + }); final audioStream = (res.data?.stream as Stream?)?.asBroadcastStream(); diff --git a/lib/provider/server/sourced_track.dart b/lib/provider/server/sourced_track.dart index 53a04023..5a6377b5 100644 --- a/lib/provider/server/sourced_track.dart +++ b/lib/provider/server/sourced_track.dart @@ -21,8 +21,9 @@ final sourcedTrackProvider = }, ); - final sourcedTrack = - await SourcedTrack.fetchFromTrack(track: track, ref: ref); + final sourcedTrack = media?.extras?["switch"] == true + ? await SourcedTrack.fetchFromTrackAltSource(track: track, ref: ref) + : await SourcedTrack.fetchFromTrack(track: track, ref: ref); return sourcedTrack; }); diff --git a/lib/services/sourced_track/models/source_info.g.dart b/lib/services/sourced_track/models/source_info.g.dart index 5fe136ce..54671f63 100644 --- a/lib/services/sourced_track/models/source_info.g.dart +++ b/lib/services/sourced_track/models/source_info.g.dart @@ -12,7 +12,7 @@ SourceInfo _$SourceInfoFromJson(Map json) => SourceInfo( artist: json['artist'] as String, thumbnail: json['thumbnail'] as String, pageUrl: json['pageUrl'] as String, - duration: Duration(microseconds: json['duration'] as int), + duration: Duration(microseconds: (json['duration'] as num).toInt()), artistUrl: json['artistUrl'] as String, album: json['album'] as String?, ); diff --git a/lib/services/sourced_track/models/video_info.dart b/lib/services/sourced_track/models/video_info.dart index 58dd0280..e3452c61 100644 --- a/lib/services/sourced_track/models/video_info.dart +++ b/lib/services/sourced_track/models/video_info.dart @@ -1,3 +1,4 @@ +import 'package:invidious/invidious.dart'; import 'package:piped_client/piped_client.dart'; import 'package:spotube/models/database/database.dart'; @@ -112,4 +113,24 @@ class YoutubeVideoInfo { channelId: stream.uploaderUrl, ); } + + factory YoutubeVideoInfo.fromSearchResponse( + InvidiousSearchResponseVideo searchResponse, + SearchMode searchMode, + ) { + return YoutubeVideoInfo( + searchMode: searchMode, + title: searchResponse.title, + duration: Duration(seconds: searchResponse.lengthSeconds), + thumbnailUrl: searchResponse.videoThumbnails.first.url, + id: searchResponse.videoId, + likes: 0, + dislikes: 0, + views: searchResponse.viewCount, + channelName: searchResponse.author, + channelId: searchResponse.authorId, + publishedAt: + DateTime.fromMillisecondsSinceEpoch(searchResponse.published * 1000), + ); + } } diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index 977b980b..6a1b7141 100644 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -12,6 +12,7 @@ import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/sourced_track/exceptions.dart'; import 'package:spotube/services/sourced_track/models/source_info.dart'; import 'package:spotube/services/sourced_track/models/source_map.dart'; +import 'package:spotube/services/sourced_track/sources/invidious.dart'; import 'package:spotube/services/sourced_track/sources/jiosaavn.dart'; import 'package:spotube/services/sourced_track/sources/piped.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; @@ -85,6 +86,13 @@ abstract class SourcedTrack extends Track { sourceInfo: sourceInfo, track: track, ), + AudioSource.invidious => InvidiousSourcedTrack( + ref: ref, + source: source, + siblings: siblings, + sourceInfo: sourceInfo, + track: track, + ), }; } @@ -104,6 +112,49 @@ abstract class SourcedTrack extends Track { return "$title - ${artists.join(", ")}"; } + static fetchFromTrackAltSource({ + required Track track, + required Ref ref, + }) async { + final preferences = ref.read(userPreferencesProvider); + try { + return switch (preferences.audioSource) { + AudioSource.piped || + AudioSource.invidious || + AudioSource.jiosaavn => + await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref), + AudioSource.youtube => + await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref), + }; + } on TrackNotFoundError catch (_) { + return switch (preferences.audioSource) { + AudioSource.piped || + AudioSource.youtube || + AudioSource.invidious => + await JioSaavnSourcedTrack.fetchFromTrack( + track: track, + ref: ref, + weakMatch: true, + ), + AudioSource.jiosaavn => + await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref), + }; + } on HttpClientClosedException catch (_) { + return await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref); + } on VideoUnplayableException catch (_) { + return await InvidiousSourcedTrack.fetchFromTrack(track: track, ref: ref); + } catch (e) { + if (e is DioException || e is ClientException || e is SocketException) { + return await JioSaavnSourcedTrack.fetchFromTrack( + track: track, + ref: ref, + weakMatch: preferences.audioSource == AudioSource.jiosaavn, + ); + } + rethrow; + } + } + static Future fetchFromTrack({ required Track track, required Ref ref, @@ -117,11 +168,14 @@ abstract class SourcedTrack extends Track { await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref), AudioSource.jiosaavn => await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref), + AudioSource.invidious => + await InvidiousSourcedTrack.fetchFromTrack(track: track, ref: ref), }; } on TrackNotFoundError catch (_) { return switch (preferences.audioSource) { AudioSource.piped || - AudioSource.youtube => + AudioSource.youtube || + AudioSource.invidious => await JioSaavnSourcedTrack.fetchFromTrack( track: track, ref: ref, @@ -159,6 +213,8 @@ abstract class SourcedTrack extends Track { YoutubeSourcedTrack.fetchSiblings(track: track, ref: ref), AudioSource.jiosaavn => JioSaavnSourcedTrack.fetchSiblings(track: track, ref: ref), + AudioSource.invidious => + InvidiousSourcedTrack.fetchSiblings(track: track, ref: ref), }; } diff --git a/lib/services/sourced_track/sources/invidious.dart b/lib/services/sourced_track/sources/invidious.dart new file mode 100644 index 00000000..26e19550 --- /dev/null +++ b/lib/services/sourced_track/sources/invidious.dart @@ -0,0 +1,263 @@ +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotify/spotify.dart'; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/services/sourced_track/enums.dart'; +import 'package:spotube/services/sourced_track/exceptions.dart'; +import 'package:spotube/services/sourced_track/models/source_info.dart'; +import 'package:spotube/services/sourced_track/models/source_map.dart'; +import 'package:spotube/services/sourced_track/models/video_info.dart'; +import 'package:spotube/services/sourced_track/sourced_track.dart'; +import 'package:invidious/invidious.dart'; +import 'package:spotube/services/sourced_track/sources/youtube.dart'; +import 'package:spotube/utils/service_utils.dart'; + +final invidiousProvider = Provider( + (ref) { + return InvidiousClient(server: "https://inv.nadeko.net"); + }, +); + +class InvidiousSourceInfo extends SourceInfo { + InvidiousSourceInfo({ + required super.id, + required super.title, + required super.artist, + required super.thumbnail, + required super.pageUrl, + required super.duration, + required super.artistUrl, + required super.album, + }); +} + +class InvidiousSourcedTrack extends SourcedTrack { + InvidiousSourcedTrack({ + required super.ref, + required super.source, + required super.siblings, + required super.sourceInfo, + required super.track, + }); + + static Future fetchFromTrack({ + required Track track, + required Ref ref, + }) async { + final database = ref.read(databaseProvider); + final cachedSource = await (database.select(database.sourceMatchTable) + ..where((s) => s.trackId.equals(track.id!)) + ..limit(1) + ..orderBy([ + (s) => + OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc), + ])) + .getSingleOrNull(); + final invidiousClient = ref.read(invidiousProvider); + + if (cachedSource == null) { + final siblings = await fetchSiblings(ref: ref, track: track); + if (siblings.isEmpty) { + throw TrackNotFoundError(track); + } + + await database.into(database.sourceMatchTable).insert( + SourceMatchTableCompanion.insert( + trackId: track.id!, + sourceId: siblings.first.info.id, + sourceType: const Value(SourceType.youtube), + ), + ); + + return InvidiousSourcedTrack( + ref: ref, + siblings: siblings.map((s) => s.info).skip(1).toList(), + source: siblings.first.source as SourceMap, + sourceInfo: siblings.first.info, + track: track, + ); + } else { + final manifest = + await invidiousClient.videos.get(cachedSource.sourceId, local: true); + + return InvidiousSourcedTrack( + ref: ref, + siblings: [], + source: toSourceMap(manifest), + sourceInfo: InvidiousSourceInfo( + id: manifest.videoId, + artist: manifest.author, + artistUrl: manifest.authorUrl, + pageUrl: "https://www.youtube.com/watch?v=${manifest.videoId}", + thumbnail: manifest.videoThumbnails.first.url, + title: manifest.title, + duration: Duration(seconds: manifest.lengthSeconds), + album: null, + ), + track: track, + ); + } + } + + static SourceMap toSourceMap(InvidiousVideoResponse manifest) { + final m4a = manifest.adaptiveFormats + .where((audio) => audio.type.contains("audio/mp4")) + .sorted((a, b) => int.parse(a.bitrate).compareTo(int.parse(b.bitrate))); + + final weba = manifest.adaptiveFormats + .where((audio) => audio.type.contains("audio/webm")) + .sorted((a, b) => int.parse(a.bitrate).compareTo(int.parse(b.bitrate))); + + return SourceMap( + m4a: SourceQualityMap( + high: m4a.first.url.toString(), + medium: (m4a.elementAtOrNull(m4a.length ~/ 2) ?? m4a[1]).url.toString(), + low: m4a.last.url.toString(), + ), + weba: SourceQualityMap( + high: weba.first.url.toString(), + medium: + (weba.elementAtOrNull(weba.length ~/ 2) ?? weba[1]).url.toString(), + low: weba.last.url.toString(), + ), + ); + } + + static Future toSiblingType( + int index, + YoutubeVideoInfo item, + InvidiousClient invidiousClient, + ) async { + SourceMap? sourceMap; + if (index == 0) { + final manifest = await invidiousClient.videos.get(item.id, local: true); + sourceMap = toSourceMap(manifest); + } + + final SiblingType sibling = ( + info: InvidiousSourceInfo( + id: item.id, + artist: item.channelName, + artistUrl: "https://www.youtube.com/${item.channelId}", + pageUrl: "https://www.youtube.com/watch?v=${item.id}", + thumbnail: item.thumbnailUrl, + title: item.title, + duration: item.duration, + album: null, + ), + source: sourceMap, + ); + + return sibling; + } + + static Future> fetchSiblings({ + required Track track, + required Ref ref, + }) async { + final invidiousClient = ref.read(invidiousProvider); + final preference = ref.read(userPreferencesProvider); + + final query = SourcedTrack.getSearchTerm(track); + + final searchResults = await invidiousClient.search.list( + query, + type: InvidiousSearchType.video, + ); + + if (ServiceUtils.onlyContainsEnglish(query)) { + return await Future.wait( + searchResults + .whereType() + .map( + (result) => YoutubeVideoInfo.fromSearchResponse( + result, + preference.searchMode, + ), + ) + .mapIndexed((i, r) => toSiblingType(i, r, invidiousClient)), + ); + } + + final rankedSiblings = YoutubeSourcedTrack.rankResults( + searchResults + .whereType() + .map( + (result) => YoutubeVideoInfo.fromSearchResponse( + result, + preference.searchMode, + ), + ) + .toList(), + track, + ); + + return await Future.wait( + rankedSiblings.mapIndexed((i, r) => toSiblingType(i, r, invidiousClient)), + ); + } + + @override + Future copyWithSibling() async { + if (siblings.isNotEmpty) { + return this; + } + final fetchedSiblings = await fetchSiblings(ref: ref, track: this); + + return InvidiousSourcedTrack( + ref: ref, + siblings: fetchedSiblings + .where((s) => s.info.id != sourceInfo.id) + .map((s) => s.info) + .toList(), + source: source, + sourceInfo: sourceInfo, + track: this, + ); + } + + @override + Future swapWithSibling(SourceInfo sibling) async { + if (sibling.id == sourceInfo.id) { + return null; + } + + // a sibling source that was fetched from the search results + final isStepSibling = siblings.none((s) => s.id == sibling.id); + + final newSourceInfo = isStepSibling + ? sibling + : siblings.firstWhere((s) => s.id == sibling.id); + final newSiblings = siblings.where((s) => s.id != sibling.id).toList() + ..insert(0, sourceInfo); + + final pipedClient = ref.read(invidiousProvider); + + final manifest = + await pipedClient.videos.get(newSourceInfo.id, local: true); + + final database = ref.read(databaseProvider); + await database.into(database.sourceMatchTable).insert( + SourceMatchTableCompanion.insert( + trackId: id!, + sourceId: newSourceInfo.id, + sourceType: const Value(SourceType.youtube), + // Because we're sorting by createdAt in the query + // we have to update it to indicate priority + createdAt: Value(DateTime.now()), + ), + mode: InsertMode.replace, + ); + + return InvidiousSourcedTrack( + ref: ref, + siblings: newSiblings, + source: toSourceMap(manifest), + sourceInfo: newSourceInfo, + track: this, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 77193ca0..d532dc4b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -518,10 +518,18 @@ packages: dependency: "direct main" description: name: dio - sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" url: "https://pub.dev" source: hosted - version: "5.4.3+1" + version: "5.7.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + url: "https://pub.dev" + source: hosted + version: "2.0.0" disable_battery_optimization: dependency: "direct main" description: @@ -993,10 +1001,10 @@ packages: dependency: "direct main" description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -1247,6 +1255,13 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + invidious: + dependency: "direct main" + description: + path: "../invidious" + relative: true + source: path + version: "0.1.0" io: dependency: "direct dev" description: @@ -1275,18 +1290,18 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b url: "https://pub.dev" source: hosted - version: "6.7.1" + version: "6.8.0" leak_tracker: dependency: transitive description: @@ -2432,5 +2447,5 @@ packages: source: hosted version: "2.2.3" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.5.3 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index df8e668d..490e7f15 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,10 @@ dependencies: html: ^0.15.1 image_picker: ^1.1.0 intl: any + invidious: + # git: + # url: https://github.com/KRTirtho/invidious_dart.git + path: ../invidious json_annotation: ^4.8.1 logger: ^2.0.2 media_kit: ^1.1.10+1 diff --git a/untranslated_messages.json b/untranslated_messages.json index 9e26dfee..0bff5a73 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1 +1,105 @@ -{} \ No newline at end of file +{ + "ar": [ + "invidious_source_description" + ], + + "bn": [ + "invidious_source_description" + ], + + "ca": [ + "invidious_source_description" + ], + + "cs": [ + "invidious_source_description" + ], + + "de": [ + "invidious_source_description" + ], + + "es": [ + "invidious_source_description" + ], + + "eu": [ + "invidious_source_description" + ], + + "fa": [ + "invidious_source_description" + ], + + "fi": [ + "invidious_source_description" + ], + + "fr": [ + "invidious_source_description" + ], + + "hi": [ + "invidious_source_description" + ], + + "id": [ + "invidious_source_description" + ], + + "it": [ + "invidious_source_description" + ], + + "ja": [ + "invidious_source_description" + ], + + "ka": [ + "invidious_source_description" + ], + + "ko": [ + "invidious_source_description" + ], + + "ne": [ + "invidious_source_description" + ], + + "nl": [ + "invidious_source_description" + ], + + "pl": [ + "invidious_source_description" + ], + + "pt": [ + "invidious_source_description" + ], + + "ru": [ + "invidious_source_description" + ], + + "th": [ + "invidious_source_description" + ], + + "tr": [ + "invidious_source_description" + ], + + "uk": [ + "invidious_source_description" + ], + + "vi": [ + "invidious_source_description" + ], + + "zh": [ + "invidious_source_description" + ] +}