fix(player): volume slider, prefetching of media_kit and stuttering on sponsorblock skip

This commit is contained in:
Kingkor Roy Tirtho 2023-05-13 19:11:02 +06:00
parent 5f70207076
commit 1f3255481f
2 changed files with 69 additions and 40 deletions

View File

@ -20,6 +20,9 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart';
/// * [x] Prefetch next track as [SpotubeTrack] on 80% of current track
/// * [ ] Mixed Queue containing both [SpotubeTrack] and [LocalTrack]
/// * [ ] Caching and loading of cache of tracks
/// * [ ] Shuffling and loop => playlist, track, none
/// * [ ] Alternative Track Source
/// * [x] Blacklisting of tracks and artist
///
/// Don'ts:
/// * It'll not have any proxy method for [SpotubeAudioPlayer]
@ -48,7 +51,8 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
notificationService = await AudioServices.create(ref, this);
audioPlayer.currentIndexChangedStream.listen((index) async {
if (index == -1) return;
if (index == -1 || index == state.active) return;
final track = state.tracks.elementAtOrNull(index);
if (track == null) return;
notificationService.addTrack(track);
@ -60,14 +64,26 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
});
bool isPreSearching = false;
audioPlayer.percentCompletedStream(80).listen((_) async {
audioPlayer.percentCompletedStream(60).listen((percent) async {
if (isPreSearching) return;
try {
isPreSearching = true;
final softReplace =
SpotubeAudioPlayer.mkSupportedPlatform && percent <= 98;
// TODO: Make repeat mode sensitive changes later
final track =
await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);
final track = await ensureNthSourcePlayable(
audioPlayer.currentIndex + 1,
/// [MediaKit] doesn't fully support replacing source, so we need
/// to check if the platform is supported or not and replace the
/// actual playlist with a playlist that contains the next track
/// at 98% >= progress
softReplace: softReplace,
exclusive: SpotubeAudioPlayer.mkSupportedPlatform,
);
if (track != null) {
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
}
@ -109,8 +125,7 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
preferences.skipSponsorSegments) {
for (final segment in activeTrack.skipSegments) {
if (pos.inSeconds < segment["start"]! ||
pos.inSeconds > segment["end"]!) continue;
await audioPlayer.pause();
pos.inSeconds >= segment["end"]!) continue;
await audioPlayer.seek(Duration(seconds: segment["end"]!));
}
}
@ -118,7 +133,11 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
}();
}
Future<SpotubeTrack?> ensureNthSourcePlayable(int n) async {
Future<SpotubeTrack?> ensureNthSourcePlayable(
int n, {
bool softReplace = false,
bool exclusive = false,
}) async {
final sources = audioPlayer.sources;
if (n < 0 || n > sources.length - 1) return null;
final nthSource = sources.elementAtOrNull(n);
@ -127,19 +146,23 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
final nthTrack = state.tracks.firstWhereOrNull(
(element) => element.id == getIdFromUnPlayable(nthSource),
);
if (nthTrack == null ||
nthTrack is SpotubeTrack ||
nthTrack is LocalTrack) {
if (nthTrack == null || nthTrack is LocalTrack) {
return null;
}
final nthFetchedTrack =
await SpotubeTrack.fetchFromTrack(nthTrack, preferences);
final nthFetchedTrack = nthTrack is SpotubeTrack
? nthTrack
: await SpotubeTrack.fetchFromTrack(nthTrack, preferences);
if (nthSource == nthFetchedTrack.ytUri) return null;
if (!softReplace) {
await audioPlayer.replaceSource(
nthSource,
nthFetchedTrack.ytUri,
exclusive: exclusive,
);
}
return nthFetchedTrack;
}

View File

@ -64,15 +64,16 @@ class SpotubeAudioPlayer {
}
/// Stream that emits when the player is almost (%) complete
Stream<void> percentCompletedStream(double percent) {
Stream<int> percentCompletedStream(double percent) {
return positionStream
.asyncMap((event) async => [event, await duration])
.where((event) {
final position = event[0] as Duration;
final duration = event[1] as Duration;
return position.inSeconds > duration.inSeconds * percent / 100;
}).asBroadcastStream();
.asyncMap(
(position) async => (await duration)?.inSeconds == 0
? 0
: (position.inSeconds / (await duration)!.inSeconds * 100)
.toInt(),
)
.where((event) => event >= percent)
.asBroadcastStream();
}
Stream<bool> get playingStream {
@ -143,12 +144,8 @@ class SpotubeAudioPlayer {
.map((event) => event.index)
.asBroadcastStream();
} else {
return _justAudio!.positionDiscontinuityStream
.where(
(event) =>
event.reason == ja.PositionDiscontinuityReason.autoAdvance,
)
.map((event) => currentIndex)
return _justAudio!.sequenceStateStream
.map((event) => event?.currentIndex ?? -1)
.asBroadcastStream();
}
}
@ -237,6 +234,7 @@ class SpotubeAudioPlayer {
}
}
/// Returns the current volume of the player, between 0 and 1
double get volume {
if (mkSupportedPlatform) {
return _mkPlayer!.state.volume / 100;
@ -320,8 +318,10 @@ class SpotubeAudioPlayer {
await _justAudio?.seek(position);
}
/// Volume is between 0 and 1
Future<void> setVolume(double volume) async {
await _mkPlayer?.setVolume(volume);
assert(volume >= 0 && volume <= 1);
await _mkPlayer?.setVolume(volume * 100);
await _justAudio?.setVolume(volume);
}
@ -391,7 +391,7 @@ class SpotubeAudioPlayer {
if (mkSupportedPlatform) {
return _mkPlayer!.state.playlist.index;
} else {
return _justAudio!.sequenceState!.currentIndex;
return _justAudio!.sequenceState?.currentIndex ?? -1;
}
}
@ -447,13 +447,18 @@ class SpotubeAudioPlayer {
}
}
Future<void> replaceSource(String oldSource, String newSource) async {
final willBeReplacedIndex = sources.indexOf(oldSource);
if (willBeReplacedIndex == -1) return;
Future<void> replaceSource(
String oldSource,
String newSource, {
bool exclusive = false,
}) async {
final oldSourceIndex = sources.indexOf(oldSource);
if (oldSourceIndex == -1) return;
if (mkSupportedPlatform) {
final sourcesCp = sources.toList();
sourcesCp[willBeReplacedIndex] = newSource;
sourcesCp[oldSourceIndex] = newSource;
await _mkPlayer!.open(
mk.Playlist(
sourcesCp.map(mk.Media.new).toList(),
@ -461,20 +466,21 @@ class SpotubeAudioPlayer {
),
play: false,
);
if (exclusive) await jumpTo(oldSourceIndex);
} else {
await addTrack(newSource);
await removeTrack(willBeReplacedIndex);
await removeTrack(oldSourceIndex);
int newSourceIndex = sources.indexOf(newSource);
while (newSourceIndex == -1) {
await Future.delayed(const Duration(milliseconds: 100));
newSourceIndex = sources.indexOf(newSource);
}
await moveTrack(newSourceIndex, willBeReplacedIndex);
await moveTrack(newSourceIndex, oldSourceIndex);
newSourceIndex = sources.indexOf(newSource);
while (newSourceIndex != willBeReplacedIndex) {
while (newSourceIndex != oldSourceIndex) {
await Future.delayed(const Duration(milliseconds: 100));
await moveTrack(newSourceIndex, willBeReplacedIndex);
await moveTrack(newSourceIndex, oldSourceIndex);
newSourceIndex = sources.indexOf(newSource);
}
}