From 8d2425b06a018e31f69daf6624e05a1e4a8c19d5 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sat, 2 Sep 2023 22:28:05 -0700 Subject: [PATCH] Playback: Start playback and seek in the background Perform playback start and seeking operations in the background, instead of on the main thread, which should help prevent them from stalling the user interface. Signed-off-by: Christopher Snowhill --- Application/PlaybackController.m | 41 +++++++++++++++++++++++++++----- Audio/AudioPlayer.h | 2 ++ Audio/AudioPlayer.m | 8 +++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/Application/PlaybackController.m b/Application/PlaybackController.m index 6d58fb397..1fe1a3284 100644 --- a/Application/PlaybackController.m +++ b/Application/PlaybackController.m @@ -23,6 +23,35 @@ extern BOOL kAppControllerShuttingDown; +@implementation NSObject (NxAdditions) + +-(void)performSelectorInBackground:(SEL)selector withObjects:(id)object, ... +{ + NSMethodSignature *signature = [self methodSignatureForSelector:selector]; + + // Setup the invocation + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.target = self; + invocation.selector = selector; + + // Associate the arguments + va_list objects; + va_start(objects, object); + unsigned int objectCounter = 2; + for (id obj = object; obj != nil; obj = va_arg(objects, id)) + { + [invocation setArgument:&obj atIndex:objectCounter++]; + } + va_end(objects); + + // Make sure to invoke on a background queue + NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithInvocation:invocation]; + NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init]; + [backgroundQueue addOperation:operation]; +} + +@end + @implementation PlaybackController #define DEFAULT_SEEK 5 @@ -241,7 +270,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { double seekTime = pe.seekable ? [offset doubleValue] : 0.0; - [audioPlayer play:pe.url withUserInfo:pe withRGInfo:makeRGInfo(pe) startPaused:paused andSeekTo:seekTime]; + [audioPlayer performSelectorInBackground:@selector(playBG:withUserInfo:withRGInfo:startPaused:andSeekTo:) withObjects:pe.url, pe, makeRGInfo(pe), @(paused), @(seekTime), nil]; } - (IBAction)next:(id)sender { @@ -272,7 +301,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { - (IBAction)seek:(id)sender { double time = [sender doubleValue]; - [audioPlayer seekToTime:time]; + [audioPlayer performSelectorInBackground:@selector(seekToTimeBG:) withObject:@(time)]; lastPosition = -10; @@ -289,7 +318,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { lastPosition = -10; - [audioPlayer seekToTime:time]; + [audioPlayer performSelectorInBackground:@selector(seekToTimeBG:) withObject:@(time)]; [self setPosition:time]; @@ -321,7 +350,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { [self next:self]; } else { lastPosition = -10; - [audioPlayer seekToTime:seekTo]; + [audioPlayer performSelectorInBackground:@selector(seekToTimeBG:) withObject:@(seekTo)]; [self setPosition:seekTo]; } } @@ -338,7 +367,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { lastPosition = -10; - [audioPlayer seekToTime:seekTo]; + [audioPlayer performSelectorInBackground:@selector(seekToTimeBG:) withObject:@(seekTo)]; [self setPosition:seekTo]; } @@ -704,7 +733,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) { PlaylistEntry *pe = [playlistController currentEntry]; BOOL paused = playbackStatus == CogStatusPaused; [[FIRCrashlytics crashlytics] logWithFormat:@"Restarting playback of track: %@", pe.url]; - [player play:pe.url withUserInfo:pe withRGInfo:makeRGInfo(pe) startPaused:paused andSeekTo:pe.seekable ? pe.currentPosition : 0.0]; + [player performSelectorInBackground:@selector(playBG:withUserInfo:withRGInfo:startPaused:andSeekTo:) withObjects:pe.url, pe, makeRGInfo(pe), @(paused), @(pe.seekable ? pe.currentPosition : 0.0), nil]; } - (void)audioPlayer:(AudioPlayer *)player pushInfo:(NSDictionary *)info toTrack:(id)userInfo { diff --git a/Audio/AudioPlayer.h b/Audio/AudioPlayer.h index 32cdaa66a..081098076 100644 --- a/Audio/AudioPlayer.h +++ b/Audio/AudioPlayer.h @@ -61,12 +61,14 @@ - (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi; - (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused; - (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused andSeekTo:(double)time; +- (void)playBG:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(NSNumber *)paused andSeekTo:(NSNumber *)time; - (void)stop; - (void)pause; - (void)resume; - (void)seekToTime:(double)time; +- (void)seekToTimeBG:(NSNumber *)time; - (void)setVolume:(double)v; - (double)volume; - (double)volumeUp:(double)amount; diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 8ad42dc85..98077054e 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -56,6 +56,10 @@ [self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:paused andSeekTo:0.0]; } +- (void)playBG:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(NSNumber *)paused andSeekTo:(NSNumber *)time { + [self play:url withUserInfo:userInfo withRGInfo:rgi startPaused:[paused boolValue] andSeekTo:[time doubleValue]]; +} + - (void)play:(NSURL *)url withUserInfo:(id)userInfo withRGInfo:(NSDictionary *)rgi startPaused:(BOOL)paused andSeekTo:(double)time { ALog(@"Opening file for playback: %@ at seek offset %f%@", url, time, (paused) ? @", starting paused" : @""); @@ -165,6 +169,10 @@ [self setPlaybackStatus:CogStatusPlaying waitUntilDone:YES]; } +- (void)seekToTimeBG:(NSNumber *)time { + [self seekToTime:[time doubleValue]]; +} + - (void)seekToTime:(double)time { if(endOfInputReached) { // This is a dirty hack in case the playback has finished with the track