mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
feat: youtube on a separate dart isolate
This commit is contained in:
parent
4c3718467d
commit
8e34430d9f
@ -18,6 +18,7 @@ import 'package:spotube/provider/audio_player/audio_player.dart';
|
||||
import 'package:spotube/provider/audio_player/querying_track_info.dart';
|
||||
import 'package:spotube/provider/server/active_sourced_track.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/isolates/yt_explode.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';
|
||||
@ -114,7 +115,8 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
||||
activeSourceInfo,
|
||||
);
|
||||
} else {
|
||||
final resultsYt = await youtubeClient.search.search(searchTerm.trim());
|
||||
final resultsYt =
|
||||
await IsolatedYoutubeExplode.instance.search(searchTerm.trim());
|
||||
|
||||
final searchResults = await Future.wait(
|
||||
resultsYt
|
||||
|
@ -11,6 +11,7 @@ import 'package:spotube/provider/audio_player/audio_player_streams.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
import 'package:spotube/provider/palette_provider.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/isolates/yt_explode.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/services/sourced_track/enums.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
@ -40,6 +41,10 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
|
||||
..where((tbl) => tbl.id.equals(0)))
|
||||
.getSingle();
|
||||
|
||||
if (state.audioSource == AudioSource.youtube) {
|
||||
await IsolatedYoutubeExplode.initialize();
|
||||
}
|
||||
|
||||
final subscription = (db.select(db.preferencesTable)
|
||||
..where((tbl) => tbl.id.equals(0)))
|
||||
.watchSingle()
|
||||
@ -207,6 +212,10 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
|
||||
|
||||
void setAudioSource(AudioSource type) {
|
||||
setData(PreferencesTableCompanion(audioSource: Value(type)));
|
||||
|
||||
if (type != AudioSource.youtube && IsolatedYoutubeExplode.isInitialized) {
|
||||
IsolatedYoutubeExplode.instance.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void setSystemTitleBar(bool isSystemTitleBar) {
|
||||
|
121
lib/services/isolates/yt_explode.dart
Normal file
121
lib/services/isolates/yt_explode.dart
Normal file
@ -0,0 +1,121 @@
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
/// A Isolate wrapper for the YoutubeExplode class
|
||||
/// 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<void> initialize() async {
|
||||
if (_instance != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final completer = Completer<SendPort>();
|
||||
|
||||
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<dynamic> arguments = message[2];
|
||||
|
||||
// Run the requested method on YoutubeExplode
|
||||
var result = switch (methodName) {
|
||||
"search" => youtubeExplode.search
|
||||
.search(arguments[0] as String, filter: TypeFilters.video)
|
||||
.then((s) => s.toList()),
|
||||
"video" => youtubeExplode.videos.get(arguments[0] as String),
|
||||
"manifest" => youtubeExplode.videos.streamsClient.getManifest(
|
||||
arguments[0] as String,
|
||||
requireWatchPage: false,
|
||||
ytClients: [
|
||||
YoutubeApiClient.mediaConnect,
|
||||
YoutubeApiClient.ios,
|
||||
YoutubeApiClient.android,
|
||||
YoutubeApiClient.mweb,
|
||||
YoutubeApiClient.tv,
|
||||
],
|
||||
),
|
||||
_ => throw ArgumentError('Invalid method name: $methodName'),
|
||||
};
|
||||
|
||||
replyPort.send(await result);
|
||||
});
|
||||
}
|
||||
|
||||
Future<T> _runMethod<T>(String methodName, List<dynamic> args) {
|
||||
final completer = Completer<T>();
|
||||
final responsePort = ReceivePort();
|
||||
|
||||
responsePort.listen((message) {
|
||||
completer.complete(message as T);
|
||||
responsePort.close();
|
||||
});
|
||||
|
||||
_sendPort.send([responsePort.sendPort, methodName, args]);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<List<Video>> search(String query) async {
|
||||
return _runMethod<List<Video>>("search", [query]);
|
||||
}
|
||||
|
||||
Future<Video> video(String videoId) async {
|
||||
return _runMethod<Video>("video", [videoId]);
|
||||
}
|
||||
|
||||
Future<StreamManifest> manifest(String videoId) async {
|
||||
return _runMethod<StreamManifest>("manifest", [videoId]);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_receivePort.close();
|
||||
_isolate.kill(priority: Isolate.immediate);
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
import 'package:spotube/services/isolates/yt_explode.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/services/song_link/song_link.dart';
|
||||
import 'package:spotube/services/sourced_track/enums.dart';
|
||||
@ -16,7 +16,6 @@ import 'package:spotube/services/sourced_track/sourced_track.dart';
|
||||
import 'package:spotube/utils/service_utils.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
final youtubeClient = YoutubeExplode();
|
||||
final officialMusicRegex = RegExp(
|
||||
r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)",
|
||||
caseSensitive: false,
|
||||
@ -48,6 +47,9 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
required Track track,
|
||||
required Ref ref,
|
||||
}) async {
|
||||
if (!IsolatedYoutubeExplode.isInitialized) {
|
||||
await IsolatedYoutubeExplode.initialize();
|
||||
}
|
||||
final database = ref.read(databaseProvider);
|
||||
final cachedSource = await (database.select(database.sourceMatchTable)
|
||||
..where((s) => s.trackId.equals(track.id!))
|
||||
@ -81,18 +83,10 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
track: track,
|
||||
);
|
||||
}
|
||||
final item = await youtubeClient.videos.get(cachedSource.sourceId);
|
||||
final manifest = await youtubeClient.videos.streamsClient.getManifest(
|
||||
cachedSource.sourceId,
|
||||
requireWatchPage: false,
|
||||
ytClients: [
|
||||
YoutubeApiClient.mediaConnect,
|
||||
YoutubeApiClient.ios,
|
||||
YoutubeApiClient.android,
|
||||
YoutubeApiClient.mweb,
|
||||
YoutubeApiClient.tv,
|
||||
],
|
||||
);
|
||||
final item =
|
||||
await IsolatedYoutubeExplode.instance.video(cachedSource.sourceId);
|
||||
final manifest =
|
||||
await IsolatedYoutubeExplode.instance.manifest(cachedSource.sourceId);
|
||||
return YoutubeSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: [],
|
||||
@ -144,17 +138,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
) async {
|
||||
SourceMap? sourceMap;
|
||||
if (index == 0) {
|
||||
final manifest = await youtubeClient.videos.streamsClient.getManifest(
|
||||
item.id,
|
||||
requireWatchPage: false,
|
||||
ytClients: [
|
||||
YoutubeApiClient.mediaConnect,
|
||||
YoutubeApiClient.ios,
|
||||
YoutubeApiClient.android,
|
||||
YoutubeApiClient.mweb,
|
||||
YoutubeApiClient.tv,
|
||||
],
|
||||
);
|
||||
final manifest = await IsolatedYoutubeExplode.instance.manifest(item.id);
|
||||
sourceMap = toSourceMap(manifest);
|
||||
}
|
||||
|
||||
@ -248,7 +232,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
await toSiblingType(
|
||||
0,
|
||||
YoutubeVideoInfo.fromVideo(
|
||||
await youtubeClient.videos.get(ytLink!.url!),
|
||||
await IsolatedYoutubeExplode.instance.video(ytLink!.url!),
|
||||
),
|
||||
)
|
||||
];
|
||||
@ -260,10 +244,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
|
||||
final query = SourcedTrack.getSearchTerm(track);
|
||||
|
||||
final searchResults = await youtubeClient.search.search(
|
||||
"$query - Topic",
|
||||
filter: TypeFilters.video,
|
||||
);
|
||||
final searchResults =
|
||||
await IsolatedYoutubeExplode.instance.search("$query - Topic");
|
||||
|
||||
if (ServiceUtils.onlyContainsEnglish(query)) {
|
||||
return await Future.wait(searchResults
|
||||
@ -294,12 +276,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
|
||||
..insert(0, sourceInfo);
|
||||
|
||||
final manifest = await youtubeClient.videos.streamsClient
|
||||
.getManifest(newSourceInfo.id)
|
||||
.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => throw ClientException("Timeout"),
|
||||
);
|
||||
final manifest =
|
||||
await IsolatedYoutubeExplode.instance.manifest(newSourceInfo.id);
|
||||
|
||||
final database = ref.read(databaseProvider);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user