diff --git a/lib/services/youtube_engine/newpipe_engine.dart b/lib/services/youtube_engine/newpipe_engine.dart index f4a87f33..ae451e22 100644 --- a/lib/services/youtube_engine/newpipe_engine.dart +++ b/lib/services/youtube_engine/newpipe_engine.dart @@ -110,4 +110,7 @@ class NewPipeEngine implements YouTubeEngine { return resultsWithVideos; } + + @override + void dispose() {} } diff --git a/lib/services/youtube_engine/youtube_engine.dart b/lib/services/youtube_engine/youtube_engine.dart index 5a8ffa4e..1c4ac7d6 100644 --- a/lib/services/youtube_engine/youtube_engine.dart +++ b/lib/services/youtube_engine/youtube_engine.dart @@ -11,4 +11,6 @@ abstract interface class YouTubeEngine { Future getStreamManifest(String videoId); Future<(Video, StreamManifest)> getVideoWithStreamInfo(String videoId); Future> searchVideos(String query); + + void dispose(); } diff --git a/lib/services/youtube_engine/youtube_explode_engine.dart b/lib/services/youtube_engine/youtube_explode_engine.dart index 15906aad..9eb45f83 100644 --- a/lib/services/youtube_engine/youtube_explode_engine.dart +++ b/lib/services/youtube_engine/youtube_explode_engine.dart @@ -1,8 +1,136 @@ +import 'dart:isolate'; + import 'package:spotube/services/youtube_engine/youtube_engine.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; +import 'dart:async'; + +/// It contains methods that are computationally expensive +class IsolatedYoutubeExplode { + final Isolate _isolate; + final SendPort _sendPort; + final ReceivePort _receivePort; + + IsolatedYoutubeExplode._( + Isolate isolate, + ReceivePort receivePort, + SendPort sendPort, + ) : _isolate = isolate, + _receivePort = receivePort, + _sendPort = sendPort; + + static IsolatedYoutubeExplode? _instance; + + static IsolatedYoutubeExplode get instance => _instance!; + + static bool get isInitialized => _instance != null; + + static Future initialize() async { + if (_instance != null) { + return; + } + + final completer = Completer(); + + final receivePort = ReceivePort(); + + /// Listen for the main isolate to set the main port + final subscription = receivePort.listen((message) { + if (message is SendPort) { + completer.complete(message); + } + }); + + final isolate = await Isolate.spawn(_isolateEntry, receivePort.sendPort); + + _instance = IsolatedYoutubeExplode._( + isolate, + receivePort, + await completer.future, + ); + + if (completer.isCompleted) { + subscription.cancel(); + } + } + + static void _isolateEntry(SendPort mainSendPort) { + final receivePort = ReceivePort(); + final youtubeExplode = YoutubeExplode(); + + /// Send the main port to the main isolate + mainSendPort.send(receivePort.sendPort); + + receivePort.listen((message) async { + final SendPort replyPort = message[0]; + final String methodName = message[1]; + final List arguments = message[2]; + + // Run the requested method on YoutubeExplode + var result = switch (methodName) { + "search" => youtubeExplode.search + .search( + arguments[0] as String, + filter: arguments.elementAtOrNull(1) ?? TypeFilters.video, + ) + .then((s) => s.toList()), + "video" => youtubeExplode.videos.get(arguments[0] as String), + "manifest" => youtubeExplode.videos.streamsClient.getManifest( + arguments[0] as String, + requireWatchPage: arguments.elementAtOrNull(1) ?? true, + ytClients: arguments.elementAtOrNull(2) as List?, + ), + _ => throw ArgumentError('Invalid method name: $methodName'), + }; + + replyPort.send(await result); + }); + } + + Future _runMethod(String methodName, List args) { + final completer = Completer(); + final responsePort = ReceivePort(); + + responsePort.listen((message) { + completer.complete(message as T); + responsePort.close(); + }); + + _sendPort.send([responsePort.sendPort, methodName, args]); + return completer.future; + } + + Future> search( + String query, { + SearchFilter? filter, + }) async { + return _runMethod>("search", [query]); + } + + Future