Handle deleting the current track gracefully

Now it should flow playback correctly to the next remaining track after
the block of deleted tracks. And if the user deletes the next queued
track, it will still be queued to flow past the deleted block. If the
user undoes their deletes and restores the tracks, playback will resume
after the originally deleted track.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-02-09 21:04:17 -08:00
parent c3cd4c34f4
commit 64c4aa2e25
4 changed files with 53 additions and 5 deletions

View File

@ -49,6 +49,8 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
PlaylistEntry *currentEntry; PlaylistEntry *currentEntry;
PlaylistEntry *nextEntryAfterDeleted;
NSUndoManager *undoManager; NSUndoManager *undoManager;
} }

View File

@ -362,6 +362,31 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(currentEntry != nil) [self.tableView scrollRowToVisible:currentEntry.index]; if(currentEntry != nil) [self.tableView scrollRowToVisible:currentEntry.index];
} }
- (void)updateNextAfterDeleted:(PlaylistEntry *)lastEntry withDeleteIndexes:(NSIndexSet *)indexes {
__block PlaylistEntry *pe = nil;
NSArray *allObjects = [self arrangedObjects];
[indexes enumerateRangesUsingBlock:^(NSRange range, BOOL *_Nonnull stop) {
if(range.location <= lastEntry.index &&
range.location + range.length > lastEntry.index) {
NSUInteger index = range.location + range.length;
if(index < [allObjects count])
pe = [allObjects objectAtIndex:index];
else
pe = nil;
} else if(pe && range.location <= [pe index] &&
range.location + range.length > [pe index]) {
NSUInteger index = range.location + range.length;
if(index < [allObjects count])
pe = [allObjects objectAtIndex:index];
else
pe = nil;
} else if(pe && range.location > [pe index]) {
*stop = YES;
}
}];
nextEntryAfterDeleted = pe;
}
- (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn { - (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn {
if([self shuffle] != ShuffleOff) [self resetShuffleList]; if([self shuffle] != ShuffleOff) [self resetShuffleList];
} }
@ -577,6 +602,10 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[NSString stringWithFormat:@"Adding %lu entries", (unsigned long)[objects count]]; [NSString stringWithFormat:@"Adding %lu entries", (unsigned long)[objects count]];
[[self undoManager] setActionName:actionName]; [[self undoManager] setActionName:actionName];
for(PlaylistEntry *pe in objects) {
pe.deleted = NO;
}
[[SQLiteStore sharedStore] playlistInsertTracks:objects [[SQLiteStore sharedStore] playlistInsertTracks:objects
atObjectIndexes:indexes atObjectIndexes:indexes
progressCall:^(double progress) { progressCall:^(double progress) {
@ -607,11 +636,14 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *pe in objects) { for(PlaylistEntry *pe in objects) {
[unarrangedIndexes addIndex:[pe index]]; [unarrangedIndexes addIndex:[pe index]];
pe.deleted = YES;
} }
if([indexes containsIndex:currentEntry.index]) { if([indexes containsIndex:currentEntry.index]) {
// Safety check. The player doesn't like committing actions on a removed track [self updateNextAfterDeleted:currentEntry withDeleteIndexes:indexes];
[playbackController stop:nil]; } else if(nextEntryAfterDeleted &&
[indexes containsIndex:nextEntryAfterDeleted.index]) {
[self updateNextAfterDeleted:nextEntryAfterDeleted withDeleteIndexes:indexes];
} }
if(currentEntry.index >= 0 && [unarrangedIndexes containsIndex:currentEntry.index]) { if(currentEntry.index >= 0 && [unarrangedIndexes containsIndex:currentEntry.index]) {
@ -828,9 +860,14 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return [self shuffledEntryAtIndex:(pe.shuffleIndex + 1)]; return [self shuffledEntryAtIndex:(pe.shuffleIndex + 1)];
} else { } else {
NSInteger i; NSInteger i;
if(pe.index < 0) // Was a current entry, now removed.
if(pe.deleted) // Was a current entry, now removed.
{ {
i = -pe.index - 1; if(nextEntryAfterDeleted)
i = nextEntryAfterDeleted.index;
else
i = 0;
nextEntryAfterDeleted = nil;
} else { } else {
i = pe.index + 1; i = pe.index + 1;
} }
@ -1038,7 +1075,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init];
if(currentEntry != nil) [refreshSet addIndex:currentEntry.index]; if(currentEntry != nil && !currentEntry.deleted) [refreshSet addIndex:currentEntry.index];
if(pe != nil) [refreshSet addIndex:pe.index]; if(pe != nil) [refreshSet addIndex:pe.index];
// Refresh entire row to refresh tooltips // Refresh entire row to refresh tooltips

View File

@ -66,6 +66,8 @@
BOOL seekable; BOOL seekable;
BOOL metadataLoaded; BOOL metadataLoaded;
BOOL deleted;
} }
+ (NSSet *)keyPathsForValuesAffectingDisplay; + (NSSet *)keyPathsForValuesAffectingDisplay;
@ -167,6 +169,8 @@
@property BOOL metadataLoaded; @property BOOL metadataLoaded;
@property BOOL deleted;
- (void)setMetadata:(NSDictionary *)metadata; - (void)setMetadata:(NSDictionary *)metadata;
@end @end

View File

@ -66,6 +66,8 @@
@synthesize metadataLoaded; @synthesize metadataLoaded;
@synthesize deleted;
// The following read-only keys depend on the values of other properties // The following read-only keys depend on the values of other properties
+ (NSSet *)keyPathsForValuesAffectingDisplay { + (NSSet *)keyPathsForValuesAffectingDisplay {
@ -139,6 +141,7 @@
self.replayGainTrackGain = 0; self.replayGainTrackGain = 0;
self.replayGainTrackPeak = 0; self.replayGainTrackPeak = 0;
self.volume = 1; self.volume = 1;
self.deleted = NO;
} }
return self; return self;
} }
@ -507,6 +510,8 @@
pe->seekable = seekable; pe->seekable = seekable;
pe->metadataLoaded = metadataLoaded; pe->metadataLoaded = metadataLoaded;
pe->deleted = deleted;
} }
return pe; return pe;