feat: implement NewPipe engine

This commit is contained in:
Kingkor Roy Tirtho 2025-02-09 23:02:43 +06:00
parent f270b2ebb9
commit 09a141b472
8 changed files with 133 additions and 7 deletions

View File

@ -38,6 +38,7 @@ android {
ndkVersion = "27.0.12077973" ndkVersion = "27.0.12077973"
compileOptions { compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
@ -120,6 +121,8 @@ flutter {
def glanceVersion = "1.1.1" def glanceVersion = "1.1.1"
dependencies { dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
// other deps so just ignore // other deps so just ignore
implementation 'com.android.support:multidex:2.0.1' implementation 'com.android.support:multidex:2.0.1'

View File

@ -51,6 +51,7 @@ import 'package:timezone/data/latest.dart' as tz;
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:yt_dlp_dart/yt_dlp_dart.dart'; import 'package:yt_dlp_dart/yt_dlp_dart.dart';
import 'package:flutter_new_pipe_extractor/flutter_new_pipe_extractor.dart';
Future<void> main(List<String> rawArgs) async { Future<void> main(List<String> rawArgs) async {
if (rawArgs.contains("web_view_title_bar")) { if (rawArgs.contains("web_view_title_bar")) {
@ -78,6 +79,7 @@ Future<void> main(List<String> rawArgs) async {
// force High Refresh Rate on some Android devices (like One Plus) // force High Refresh Rate on some Android devices (like One Plus)
if (kIsAndroid) { if (kIsAndroid) {
await FlutterDisplayMode.setHighRefreshRate(); await FlutterDisplayMode.setHighRefreshRate();
await NewPipeExtractor.init();
} }
if (!kIsWeb) { if (!kIsWeb) {

View File

@ -18,6 +18,7 @@ import 'package:spotube/services/sourced_track/enums.dart';
import 'package:flutter/widgets.dart' hide Table, Key, View; import 'package:flutter/widgets.dart' hide Table, Key, View;
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:spotube/services/youtube_engine/newpipe_engine.dart';
import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart'; import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart';
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart'; import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
import 'package:sqlite3/sqlite3.dart'; import 'package:sqlite3/sqlite3.dart';

View File

@ -34,8 +34,7 @@ enum YoutubeClientEngine {
YoutubeClientEngine.youtubeExplode => YoutubeClientEngine.youtubeExplode =>
YouTubeExplodeEngine.isAvailableForPlatform, YouTubeExplodeEngine.isAvailableForPlatform,
YoutubeClientEngine.ytDlp => YtDlpEngine.isAvailableForPlatform, YoutubeClientEngine.ytDlp => YtDlpEngine.isAvailableForPlatform,
// TODO: Implement new pipe support YoutubeClientEngine.newPipe => NewPipeEngine.isAvailableForPlatform,
YoutubeClientEngine.newPipe => false,
}; };
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/youtube_engine/newpipe_engine.dart';
import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart'; import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart';
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart'; import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
@ -9,8 +10,9 @@ final youtubeEngineProvider = Provider((ref) {
userPreferencesProvider.select((value) => value.youtubeClientEngine), userPreferencesProvider.select((value) => value.youtubeClientEngine),
); );
if (engineMode == YoutubeClientEngine.newPipe) { if (engineMode == YoutubeClientEngine.newPipe &&
throw UnimplementedError(); NewPipeEngine.isAvailableForPlatform) {
return NewPipeEngine();
} else if (engineMode == YoutubeClientEngine.ytDlp && } else if (engineMode == YoutubeClientEngine.ytDlp &&
YtDlpEngine.isAvailableForPlatform) { YtDlpEngine.isAvailableForPlatform) {
return YtDlpEngine(); return YtDlpEngine();

View File

@ -0,0 +1,109 @@
import 'package:flutter_new_pipe_extractor/flutter_new_pipe_extractor.dart'
hide Engagement;
import 'package:spotube/services/youtube_engine/youtube_engine.dart';
import 'package:spotube/utils/platform.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
import 'package:http_parser/http_parser.dart';
class NewPipeEngine implements YouTubeEngine {
static bool get isAvailableForPlatform => kIsAndroid;
AudioOnlyStreamInfo _parseAudioStream(AudioStream stream, String videoId) {
return AudioOnlyStreamInfo(
VideoId(videoId),
stream.itag,
Uri.parse(stream.content),
StreamContainer.parse(stream.mediaFormat!.mimeType.split("/").last),
FileSize.unknown,
Bitrate(stream.bitrate),
stream.codec,
stream.quality,
[],
MediaType.parse(stream.mediaFormat!.mimeType),
null,
);
}
Video _parseVideo(VideoInfo info) {
return Video(
VideoId(info.id),
info.name,
info.uploaderName,
ChannelId(info.uploaderUrl),
info.uploadDate.offsetDateTime,
info.uploadDate.offsetDateTime.toString(),
info.uploadDate.offsetDateTime,
info.description.content ?? "",
Duration(seconds: info.duration),
ThumbnailSet(info.id),
info.tags,
Engagement(
info.viewCount,
info.likeCount,
info.dislikeCount,
),
!info.streamType.name.toLowerCase().contains("live"),
);
}
Video _parseVideoResult(VideoSearchResultItem info) {
final id = Uri.parse(info.url).queryParameters["v"]!;
return Video(
VideoId(id),
info.name,
info.uploaderName,
ChannelId(info.uploaderUrl),
info.uploadDate?.offsetDateTime,
info.uploadDate?.offsetDateTime.toString(),
info.uploadDate?.offsetDateTime,
info.shortDescription ?? "",
Duration(seconds: info.duration),
ThumbnailSet(id),
[],
Engagement(info.viewCount, null, null),
!info.streamType.name.toLowerCase().contains("live"),
);
}
@override
Future<StreamManifest> getStreamManifest(String videoId) async {
final video = await NewPipeExtractor.getVideoInfo(videoId);
final streams =
video.audioStreams.map((stream) => _parseAudioStream(stream, videoId));
return StreamManifest(streams);
}
@override
Future<Video> getVideo(String videoId) async {
final video = await NewPipeExtractor.getVideoInfo(videoId);
return _parseVideo(video);
}
@override
Future<(Video, StreamManifest)> getVideoWithStreamInfo(String videoId) async {
final video = await NewPipeExtractor.getVideoInfo(videoId);
final streams =
video.audioStreams.map((stream) => _parseAudioStream(stream, videoId));
return (_parseVideo(video), StreamManifest(streams));
}
@override
Future<List<Video>> searchVideos(String query) async {
final results = await NewPipeExtractor.search(
query,
contentFilters: [SearchContentFilters.musicSongs],
);
final resultsWithVideos = results
.whereType<VideoSearchResultItem>()
.map((e) => _parseVideoResult(e))
.toList();
return resultsWithVideos;
}
}

View File

@ -914,6 +914,13 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "2.4.3"
flutter_new_pipe_extractor:
dependency: "direct main"
description:
path: "../flutter_new_pipe_extractor"
relative: true
source: path
version: "0.1.0"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@ -1210,13 +1217,13 @@ packages:
source: hosted source: hosted
version: "3.2.1" version: "3.2.1"
http_parser: http_parser:
dependency: transitive dependency: "direct main"
description: description:
name: http_parser name: http_parser
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.1" version: "4.1.2"
image: image:
dependency: transitive dependency: transitive
description: description:

View File

@ -144,6 +144,9 @@ dependencies:
git: git:
url: https://github.com/KRTirtho/yt_dlp_dart.git url: https://github.com/KRTirtho/yt_dlp_dart.git
ref: e2d82305fab18566408d6f8758361017d1640c3d ref: e2d82305fab18566408d6f8758361017d1640c3d
flutter_new_pipe_extractor:
path: ../flutter_new_pipe_extractor
http_parser: ^4.1.2
dev_dependencies: dev_dependencies:
build_runner: ^2.4.13 build_runner: ^2.4.13