Playlists, looping, shuffling for iOS
This commit is contained in:
parent
c0c5d0c2bf
commit
a63ef2ba39
44 changed files with 1629 additions and 362 deletions
File diff suppressed because it is too large
Load diff
37
darwin/Classes/AudioSource.m
Normal file
37
darwin/Classes/AudioSource.m
Normal file
|
@ -0,0 +1,37 @@
|
|||
#import "AudioSource.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation AudioSource {
|
||||
NSString *_sourceId;
|
||||
}
|
||||
|
||||
- (instancetype)initWithId:(NSString *)sid {
|
||||
self = [super init];
|
||||
NSAssert(self, @"super init cannot be nil");
|
||||
_sourceId = sid;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)sourceId {
|
||||
return _sourceId;
|
||||
}
|
||||
|
||||
- (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches {
|
||||
if ([_sourceId isEqualToString:sourceId]) {
|
||||
[matches addObject:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)getShuffleOrder {
|
||||
return @[];
|
||||
}
|
||||
|
||||
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@end
|
67
darwin/Classes/ClippingAudioSource.m
Normal file
67
darwin/Classes/ClippingAudioSource.m
Normal file
|
@ -0,0 +1,67 @@
|
|||
#import "AudioSource.h"
|
||||
#import "ClippingAudioSource.h"
|
||||
#import "IndexedPlayerItem.h"
|
||||
#import "UriAudioSource.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation ClippingAudioSource {
|
||||
UriAudioSource *_audioSource;
|
||||
CMTime _start;
|
||||
CMTime _end;
|
||||
}
|
||||
|
||||
- (instancetype)initWithId:(NSString *)sid audioSource:(UriAudioSource *)audioSource start:(NSNumber *)start end:(NSNumber *)end {
|
||||
self = [super initWithId:sid];
|
||||
NSAssert(self, @"super init cannot be nil");
|
||||
_audioSource = audioSource;
|
||||
_start = start == [NSNull null] ? kCMTimeZero : CMTimeMake([start intValue], 1000);
|
||||
_end = end == [NSNull null] ? kCMTimeInvalid : CMTimeMake([end intValue], 1000);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches {
|
||||
[super findById:sourceId matches:matches];
|
||||
[_audioSource findById:sourceId matches:matches];
|
||||
}
|
||||
|
||||
- (void)attach:(AVQueuePlayer *)player {
|
||||
[super attach:player];
|
||||
_audioSource.playerItem.forwardPlaybackEndTime = _end;
|
||||
// XXX: Not needed since currentItem observer handles it?
|
||||
[self seek:kCMTimeZero];
|
||||
}
|
||||
|
||||
- (IndexedPlayerItem *)playerItem {
|
||||
return _audioSource.playerItem;
|
||||
}
|
||||
|
||||
- (NSArray *)getShuffleOrder {
|
||||
return @[@(0)];
|
||||
}
|
||||
|
||||
- (void)play:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)pause:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)stop:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler {
|
||||
CMTime absPosition = CMTimeAdd(_start, position);
|
||||
[_audioSource.playerItem seekToTime:absPosition toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
- (CMTime)duration {
|
||||
return CMTimeSubtract(CMTIME_IS_INVALID(_end) ? self.playerItem.duration : _end, _start);
|
||||
}
|
||||
|
||||
- (void)setDuration:(CMTime)duration {
|
||||
}
|
||||
|
||||
- (CMTime)position {
|
||||
return CMTimeSubtract(self.playerItem.currentTime, _start);
|
||||
}
|
||||
|
||||
@end
|
109
darwin/Classes/ConcatenatingAudioSource.m
Normal file
109
darwin/Classes/ConcatenatingAudioSource.m
Normal file
|
@ -0,0 +1,109 @@
|
|||
#import "AudioSource.h"
|
||||
#import "ConcatenatingAudioSource.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <stdlib.h>
|
||||
|
||||
@implementation ConcatenatingAudioSource {
|
||||
NSMutableArray<AudioSource *> *_audioSources;
|
||||
NSMutableArray<NSNumber *> *_shuffleOrder;
|
||||
}
|
||||
|
||||
- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources {
|
||||
self = [super initWithId:sid];
|
||||
NSAssert(self, @"super init cannot be nil");
|
||||
_audioSources = audioSources;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int)count {
|
||||
return _audioSources.count;
|
||||
}
|
||||
|
||||
- (void)insertSource:(AudioSource *)audioSource atIndex:(int)index {
|
||||
[_audioSources insertObject:audioSource atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeSourcesFromIndex:(int)start toIndex:(int)end {
|
||||
if (end == -1) end = _audioSources.count;
|
||||
for (int i = start; i < end; i++) {
|
||||
[_audioSources removeObjectAtIndex:start];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex {
|
||||
AudioSource *source = _audioSources[currentIndex];
|
||||
[_audioSources removeObjectAtIndex:currentIndex];
|
||||
[_audioSources insertObject:source atIndex:newIndex];
|
||||
}
|
||||
|
||||
- (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex {
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
treeIndex = [_audioSources[i] buildSequence:sequence treeIndex:treeIndex];
|
||||
}
|
||||
return treeIndex;
|
||||
}
|
||||
|
||||
- (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches {
|
||||
[super findById:sourceId matches:matches];
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
[_audioSources[i] findById:sourceId matches:matches];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)getShuffleOrder {
|
||||
NSMutableArray *order = [NSMutableArray new];
|
||||
int offset = [order count];
|
||||
NSMutableArray *childOrders = [NSMutableArray new]; // array of array of ints
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
AudioSource *audioSource = _audioSources[i];
|
||||
NSArray *childShuffleOrder = [audioSource getShuffleOrder];
|
||||
NSMutableArray *offsetChildShuffleOrder = [NSMutableArray new];
|
||||
for (int j = 0; j < [childShuffleOrder count]; j++) {
|
||||
[offsetChildShuffleOrder addObject:@([childShuffleOrder[j] integerValue] + offset)];
|
||||
}
|
||||
[childOrders addObject:offsetChildShuffleOrder];
|
||||
offset += [childShuffleOrder count];
|
||||
}
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
[order addObjectsFromArray:childOrders[[_shuffleOrder[i] integerValue]]];
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex {
|
||||
int currentChildIndex = -1;
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
int indexBefore = treeIndex;
|
||||
AudioSource *child = _audioSources[i];
|
||||
treeIndex = [child shuffle:treeIndex currentIndex:currentIndex];
|
||||
if (currentIndex >= indexBefore && currentIndex < treeIndex) {
|
||||
currentChildIndex = i;
|
||||
} else {}
|
||||
}
|
||||
// Shuffle so that the current child is first in the shuffle order
|
||||
_shuffleOrder = [NSMutableArray arrayWithCapacity:[_audioSources count]];
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
[_shuffleOrder addObject:@(0)];
|
||||
}
|
||||
NSLog(@"shuffle: audioSources.count=%d and shuffleOrder.count=%d", [_audioSources count], [_shuffleOrder count]);
|
||||
// First generate a random shuffle
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
int j = arc4random_uniform(i + 1);
|
||||
_shuffleOrder[i] = _shuffleOrder[j];
|
||||
_shuffleOrder[j] = @(i);
|
||||
}
|
||||
// Then bring currentIndex to the front
|
||||
if (currentChildIndex != -1) {
|
||||
for (int i = 1; i < [_audioSources count]; i++) {
|
||||
if ([_shuffleOrder[i] integerValue] == currentChildIndex) {
|
||||
NSNumber *v = _shuffleOrder[0];
|
||||
_shuffleOrder[0] = _shuffleOrder[i];
|
||||
_shuffleOrder[i] = v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return treeIndex;
|
||||
}
|
||||
|
||||
@end
|
64
darwin/Classes/IndexedAudioSource.m
Normal file
64
darwin/Classes/IndexedAudioSource.m
Normal file
|
@ -0,0 +1,64 @@
|
|||
#import "IndexedAudioSource.h"
|
||||
#import "IndexedPlayerItem.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation IndexedAudioSource {
|
||||
BOOL _isAttached;
|
||||
}
|
||||
|
||||
- (instancetype)initWithId:(NSString *)sid {
|
||||
self = [super init];
|
||||
NSAssert(self, @"super init cannot be nil");
|
||||
_isAttached = NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (IndexedPlayerItem *)playerItem {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isAttached {
|
||||
return _isAttached;
|
||||
}
|
||||
|
||||
- (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex {
|
||||
[sequence addObject:self];
|
||||
return treeIndex + 1;
|
||||
}
|
||||
|
||||
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex {
|
||||
return treeIndex + 1;
|
||||
}
|
||||
|
||||
- (void)attach:(AVQueuePlayer *)player {
|
||||
_isAttached = YES;
|
||||
}
|
||||
|
||||
- (void)play:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)pause:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)stop:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)seek:(CMTime)position {
|
||||
[self seek:position completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler {
|
||||
}
|
||||
|
||||
- (CMTime)duration {
|
||||
return kCMTimeInvalid;
|
||||
}
|
||||
|
||||
- (void)setDuration:(CMTime)duration {
|
||||
}
|
||||
|
||||
- (CMTime)position {
|
||||
return kCMTimeInvalid;
|
||||
}
|
||||
|
||||
@end
|
16
darwin/Classes/IndexedPlayerItem.m
Normal file
16
darwin/Classes/IndexedPlayerItem.m
Normal file
|
@ -0,0 +1,16 @@
|
|||
#import "IndexedPlayerItem.h"
|
||||
#import "IndexedAudioSource.h"
|
||||
|
||||
@implementation IndexedPlayerItem {
|
||||
IndexedAudioSource *_audioSource;
|
||||
}
|
||||
|
||||
-(void)setAudioSource:(IndexedAudioSource *)audioSource {
|
||||
_audioSource = audioSource;
|
||||
}
|
||||
|
||||
-(IndexedAudioSource *)audioSource {
|
||||
return _audioSource;
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,6 +1,5 @@
|
|||
#import "JustAudioPlugin.h"
|
||||
#import "AudioPlayer.h"
|
||||
#import "AudioPlayer.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation JustAudioPlugin {
|
||||
|
|
53
darwin/Classes/LoopingAudioSource.m
Normal file
53
darwin/Classes/LoopingAudioSource.m
Normal file
|
@ -0,0 +1,53 @@
|
|||
#import "AudioSource.h"
|
||||
#import "LoopingAudioSource.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation LoopingAudioSource {
|
||||
// An array of duplicates
|
||||
NSArray<AudioSource *> *_audioSources; // <AudioSource *>
|
||||
}
|
||||
|
||||
- (instancetype)initWithId:(NSString *)sid audioSources:(NSArray<AudioSource *> *)audioSources {
|
||||
self = [super initWithId:sid];
|
||||
NSAssert(self, @"super init cannot be nil");
|
||||
_audioSources = audioSources;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex {
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
treeIndex = [_audioSources[i] buildSequence:sequence treeIndex:treeIndex];
|
||||
}
|
||||
return treeIndex;
|
||||
}
|
||||
|
||||
- (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches {
|
||||
[super findById:sourceId matches:matches];
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
[_audioSources[i] findById:sourceId matches:matches];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)getShuffleOrder {
|
||||
NSMutableArray *order = [NSMutableArray new];
|
||||
int offset = [order count];
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
AudioSource *audioSource = _audioSources[i];
|
||||
NSArray *childShuffleOrder = [audioSource getShuffleOrder];
|
||||
for (int j = 0; j < [childShuffleOrder count]; j++) {
|
||||
[order addObject:@([childShuffleOrder[j] integerValue] + offset)];
|
||||
}
|
||||
offset += [childShuffleOrder count];
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex {
|
||||
// TODO: This should probably shuffle the same way on all duplicates.
|
||||
for (int i = 0; i < [_audioSources count]; i++) {
|
||||
treeIndex = [_audioSources[i] shuffle:treeIndex currentIndex:currentIndex];
|
||||
}
|
||||
return treeIndex;
|
||||
}
|
||||
|
||||
@end
|
66
darwin/Classes/UriAudioSource.m
Normal file
66
darwin/Classes/UriAudioSource.m
Normal file
|
@ -0,0 +1,66 @@
|
|||
#import "UriAudioSource.h"
|
||||
#import "IndexedAudioSource.h"
|
||||
#import "IndexedPlayerItem.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@implementation UriAudioSource {
|
||||
NSString *_uri;
|
||||
IndexedPlayerItem *_playerItem;
|
||||
/* CMTime _duration; */
|
||||
}
|
||||
|
||||
- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri {
|
||||
self = [super initWithId:sid];
|
||||
NSAssert(self, @"super init cannot be nil");
|
||||
_uri = uri;
|
||||
if ([_uri hasPrefix:@"file://"]) {
|
||||
_playerItem = [[IndexedPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[_uri substringFromIndex:7]]];
|
||||
} else {
|
||||
_playerItem = [[IndexedPlayerItem alloc] initWithURL:[NSURL URLWithString:_uri]];
|
||||
}
|
||||
if (@available(macOS 10.13, iOS 11.0, *)) {
|
||||
// This does the best at reducing distortion on voice with speeds below 1.0
|
||||
_playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmTimeDomain;
|
||||
}
|
||||
/* NSKeyValueObservingOptions options = */
|
||||
/* NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew; */
|
||||
/* [_playerItem addObserver:self */
|
||||
/* forKeyPath:@"duration" */
|
||||
/* options:options */
|
||||
/* context:nil]; */
|
||||
return self;
|
||||
}
|
||||
|
||||
- (IndexedPlayerItem *)playerItem {
|
||||
return _playerItem;
|
||||
}
|
||||
|
||||
- (NSArray *)getShuffleOrder {
|
||||
return @[@(0)];
|
||||
}
|
||||
|
||||
- (void)play:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)pause:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)stop:(AVQueuePlayer *)player {
|
||||
}
|
||||
|
||||
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler {
|
||||
[_playerItem seekToTime:position toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
|
||||
}
|
||||
|
||||
- (CMTime)duration {
|
||||
return _playerItem.duration;
|
||||
}
|
||||
|
||||
- (void)setDuration:(CMTime)duration {
|
||||
}
|
||||
|
||||
- (CMTime)position {
|
||||
return _playerItem.currentTime;
|
||||
}
|
||||
|
||||
@end
|
Loading…
Add table
Add a link
Reference in a new issue