Compare commits

...

4 Commits

Author SHA1 Message Date
Christopher Snowhill 72ee38ad14
Audio Player: Only wait for unstopped input
Input thread now signals when it has stopped and is about to return, in
case the input thread returns before the BufferChain dealloc function
would be waiting for it to terminate. Somehow, even though the Semaphore
is being signaled at this point, the BufferChain still ends up waiting
the default of 2.5 seconds for the signal that apparently never comes,
delaying file stoppage. This prevents the wait action entirely. Must
have been some sort of race condition.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-12-09 21:18:56 -08:00
Christopher Snowhill 6daa0de425
Audio Player: Add new method of signaling stop
This new method should cause all stops to default to immediate stoppage,
and only stops that occur after an end of track signal should indicate
to play out the entire buffer.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-12-09 21:18:47 -08:00
Christopher Snowhill 1b9f460538
Playback Controller: Remove "stopping" status use
This should not really be necessary for proper player operation any
longer, and can safely be removed.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-12-09 21:18:07 -08:00
Christopher Snowhill 1de501a64a
Sandbox: Rework several blocking actions
Several actions have been reworked to be non-blocking, as their
operation should still occur in the main thread, but should not block
the thread they are called from, as they are not required to continue
processing there.

End of secure access has also been made non-blocking, as it is usually
only called when an input is done accessing a given file or folder, so
it should be important to return quickly, as the input is likely about
to terminate, and other things are waiting for it to return.

Also remove a nested block call for the storage access, as it is within
an existing serializing block, so it shouldn't need to be nested.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-12-09 21:18:03 -08:00
10 changed files with 82 additions and 50 deletions

View File

@ -677,14 +677,6 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
break;
}
if(status == CogStatusStopped) {
status = CogStatusStopping;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if([self playbackStatus] == CogStatusStopping)
[self setPlaybackStatus:CogStatusStopped];
});
}
[self setPlaybackStatus:status];
// If we don't send it here, if we've stopped, then the NPIC will be stuck at the last file we played.
[self sendMetaData];

View File

@ -315,6 +315,8 @@
// if there's already one at the head of chainQueue... r-r-right?
for(BufferChain *chain in chainQueue) {
if([chain isRunning]) {
if(output)
[output setShouldPlayOutBuffer:YES];
atomic_fetch_sub(&refCount, 1);
return YES;
}
@ -338,6 +340,8 @@
while(duration >= 30.0 && shouldContinue) {
[semaphore wait];
if(atomic_load_explicit(&resettingNow, memory_order_relaxed)) {
if(output)
[output setShouldPlayOutBuffer:YES];
atomic_fetch_sub(&refCount, 1);
return YES;
}
@ -357,6 +361,8 @@
[self requestNextStream:nextStreamUserInfo];
if(!nextStream) {
if(output)
[output setShouldPlayOutBuffer:YES];
atomic_fetch_sub(&refCount, 1);
return YES;
}
@ -407,6 +413,8 @@
while(shouldContinue && ![newChain open:url withUserInfo:nextStreamUserInfo withRGInfo:nextStreamRGInfo]) {
if(nextStream == nil) {
newChain = nil;
if(output)
[output setShouldPlayOutBuffer:YES];
atomic_fetch_sub(&refCount, 1);
return YES;
}
@ -416,6 +424,8 @@
if([nextStream isEqualTo:url]) {
newChain = nil;
if(output)
[output setShouldPlayOutBuffer:YES];
atomic_fetch_sub(&refCount, 1);
return YES;
}
@ -442,6 +452,9 @@
// - self.nextStreamUserInfo == next playlist entry
// - head of chainQueue is the buffer chain for the next entry (which has launched its threads already)
if(output)
[output setShouldPlayOutBuffer:YES];
atomic_fetch_sub(&refCount, 1);
return YES;
}

View File

@ -156,7 +156,8 @@
[inputNode setShouldContinue:NO];
[[inputNode exitAtTheEndOfTheStream] signal];
[[inputNode semaphore] signal];
[[inputNode exitAtTheEndOfTheStream] wait]; // wait for decoder to be closed (see InputNode's -(void)process )
if(![inputNode threadExited])
[[inputNode exitAtTheEndOfTheStream] wait]; // wait for decoder to be closed (see InputNode's -(void)process )
DLog(@"Bufferchain dealloc");
}

View File

@ -34,6 +34,7 @@
Semaphore *exitAtTheEndOfTheStream;
}
@property(readonly) Semaphore *exitAtTheEndOfTheStream;
@property(readonly) BOOL threadExited;
- (BOOL)openWithSource:(id<CogSource>)source;
- (BOOL)openWithDecoder:(id<CogDecoder>)d;

View File

@ -21,12 +21,14 @@
static void *kInputNodeContext = &kInputNodeContext;
@synthesize threadExited;
@synthesize exitAtTheEndOfTheStream;
- (id)initWithController:(id)c previous:(id)p {
self = [super initWithController:c previous:p];
if(self) {
exitAtTheEndOfTheStream = [[Semaphore alloc] init];
threadExited = NO;
}
return self;
@ -223,6 +225,7 @@ static void *kInputNodeContext = &kInputNodeContext;
[decoder close];
[exitAtTheEndOfTheStream signal];
threadExited = YES;
DLog("Input node thread stopping");
}

View File

@ -63,6 +63,8 @@
- (void)setShouldContinue:(BOOL)s;
- (void)setShouldPlayOutBuffer:(BOOL)s;
- (void)pause;
- (void)resume;

View File

@ -170,6 +170,10 @@
// [output stop];
}
- (void)setShouldPlayOutBuffer:(BOOL)s {
[output setShouldPlayOutBuffer:s];
}
- (BOOL)isPaused {
return paused;
}

View File

@ -116,6 +116,8 @@ using std::atomic_long;
FSurroundFilter *fsurround;
BOOL resetStreamFormat;
BOOL shouldPlayOutBuffer;
float *samplePtr;
float tempBuffer[512 * 32];
@ -149,6 +151,8 @@ using std::atomic_long;
- (void)setEqualizerEnabled:(BOOL)enabled;
- (void)setShouldPlayOutBuffer:(BOOL)enabled;
- (void)sustainHDCD;
@end

View File

@ -342,6 +342,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
- (void)threadEntry:(id)arg {
running = YES;
started = NO;
shouldPlayOutBuffer = NO;
secondsLatency = 1.0;
while(!stopping) {
@ -940,6 +941,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
stopInvoked = NO;
stopCompleted = NO;
commandStop = NO;
shouldPlayOutBuffer = NO;
audioFormatDescription = NULL;
@ -1208,7 +1210,7 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
}
if(renderSynchronizer || audioRenderer) {
if(renderSynchronizer) {
if(!commandStop) {
if(shouldPlayOutBuffer && !commandStop) {
int compareVal = 0;
double secondsLatency = self->secondsLatency >= 0 ? self->secondsLatency : 0;
int compareMax = (((1000000 / 5000) * secondsLatency) + (10000 / 5000)); // latency plus 10ms, divide by sleep intervals
@ -1302,4 +1304,8 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons
secondsHdcdSustained = 10.0;
}
- (void)setShouldPlayOutBuffer:(BOOL)s {
shouldPlayOutBuffer = s;
}
@end

View File

@ -218,10 +218,18 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
static inline void dispatch_async_reentrant(dispatch_queue_t queue, dispatch_block_t block) {
if(dispatch_queue_get_label(queue) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) {
block();
} else {
dispatch_async(queue, block);
}
}
- (void)addFolderIfMissing:(NSURL *)folderUrl {
if(![folderUrl isFileURL]) return;
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
dispatch_async_reentrant(dispatch_get_main_queue(), ^{
SandboxEntry *_entry = nil;
for(SandboxEntry *entry in self->storage) {
@ -263,7 +271,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSURL *url = [SandboxBroker urlWithoutFragment:fileUrl];
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
dispatch_async_reentrant(dispatch_get_main_queue(), ^{
SandboxEntry *_entry = nil;
for(SandboxEntry *entry in self->storage) {
@ -322,49 +330,47 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
if(!_entry) {
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
static BOOL warnedYet = NO;
static BOOL warnedYet = NO;
if(!warnedYet) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"GrantPathTitle", @"Title of file dialog for granting folder access")];
[alert setInformativeText:NSLocalizedString(@"GrantPathMessage", @"Message to new users regarding file permissions")];
[alert addButtonWithTitle:NSLocalizedString(@"GrantPathOK", @"OK button text")];
[alert addButtonWithTitle:NSLocalizedString(@"GrantPathStopWarning", @"Button to stop warnings for session")];
if(!warnedYet) {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"GrantPathTitle", @"Title of file dialog for granting folder access")];
[alert setInformativeText:NSLocalizedString(@"GrantPathMessage", @"Message to new users regarding file permissions")];
[alert addButtonWithTitle:NSLocalizedString(@"GrantPathOK", @"OK button text")];
[alert addButtonWithTitle:NSLocalizedString(@"GrantPathStopWarning", @"Button to stop warnings for session")];
if([alert runModal] == NSAlertSecondButtonReturn) {
warnedYet = YES;
}
}
if([alert runModal] == NSAlertSecondButtonReturn) {
warnedYet = YES;
}
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:NO];
[panel setFloatingPanel:YES];
[panel setDirectoryURL:folderUrl];
[panel setTitle:NSLocalizedString(@"GrantPathTitle", @"Title of file dialog for granting folder access")];
NSInteger result = [panel runModal];
if(result == NSModalResponseOK) {
NSURL *folderUrl = [panel URL];
NSError *err = nil;
NSData *bookmark = [folderUrl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&err];
if(!bookmark && err) {
ALog(@"Failed to add bookmark for URL: %@, with error: %@", folderUrl, [err localizedDescription]);
return;
}
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:NO];
[panel setFloatingPanel:YES];
[panel setDirectoryURL:folderUrl];
[panel setTitle:NSLocalizedString(@"GrantPathTitle", @"Title of file dialog for granting folder access")];
NSInteger result = [panel runModal];
if(result == NSModalResponseOK) {
NSURL *folderUrl = [panel URL];
NSError *err = nil;
NSData *bookmark = [folderUrl bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&err];
if(!bookmark && err) {
ALog(@"Failed to add bookmark for URL: %@, with error: %@", folderUrl, [err localizedDescription]);
return;
}
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext];
SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext];
if(token) {
token.path = [folderUrl path];
token.bookmark = bookmark;
[SandboxBroker cleanupFolderAccess];
}
if(token) {
token.path = [folderUrl path];
token.bookmark = bookmark;
[SandboxBroker cleanupFolderAccess];
}
});
}
}
});
}
@ -376,7 +382,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
request.sortDescriptors = @[sortDescriptor];
[pc.viewContext performBlockAndWait:^{
[pc.viewContext performBlock:^{
NSError *error = nil;
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
NSMutableArray *resultsCopy = nil;
@ -455,7 +461,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
SandboxEntry *entry = CFBridgingRelease(handle);
if(!entry) return;
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
dispatch_async_reentrant(dispatch_get_main_queue(), ^{
if(entry.refCount > 1) {
entry.refCount -= 1;
} else {