Skip to content

Commit

Permalink
fix(player): volume slider, prefetching of media_kit and stuttering o…
Browse files Browse the repository at this point in the history
…n sponsorblock skip
  • Loading branch information
Kingkor Roy Tirtho committed May 13, 2023
1 parent 5f70207 commit 1f32554
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 40 deletions.
55 changes: 39 additions & 16 deletions lib/provider/proxy_playlist/proxy_playlist_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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);
Expand All @@ -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));
}
Expand Down Expand Up @@ -109,16 +125,19 @@ 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"]!));
}
}
});
}();
}

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);
Expand All @@ -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);

await audioPlayer.replaceSource(
nthSource,
nthFetchedTrack.ytUri,
);
if (nthSource == nthFetchedTrack.ytUri) return null;

if (!softReplace) {
await audioPlayer.replaceSource(
nthSource,
nthFetchedTrack.ytUri,
exclusive: exclusive,
);
}

return nthFetchedTrack;
}
Expand Down
54 changes: 30 additions & 24 deletions lib/services/audio_player/audio_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -391,7 +391,7 @@ class SpotubeAudioPlayer {
if (mkSupportedPlatform) {
return _mkPlayer!.state.playlist.index;
} else {
return _justAudio!.sequenceState!.currentIndex;
return _justAudio!.sequenceState?.currentIndex ?? -1;
}
}

Expand Down Expand Up @@ -447,34 +447,40 @@ 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(),
index: currentIndex,
),
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);
}
}
Expand Down

0 comments on commit 1f32554

Please sign in to comment.