Updated packages, rewrote player = gapless playback, faster loading

This commit is contained in:
exttex 2020-08-13 19:39:22 +02:00
parent 6f250df004
commit d4299f736f
92 changed files with 10270 additions and 1450 deletions

View file

@ -0,0 +1,21 @@
#import <FlutterMacOS/FlutterMacOS.h>
@interface AudioPlayer : NSObject<FlutterStreamHandler>
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam configuredSession:(BOOL)configuredSession;
@end
enum ProcessingState {
none,
loading,
buffering,
ready,
completed
};
enum LoopMode {
loopOff,
loopOne,
loopAll
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
#import <FlutterMacOS/FlutterMacOS.h>
@interface AudioSource : NSObject
@property (readonly, nonatomic) NSString* sourceId;
- (instancetype)initWithId:(NSString *)sid;
- (int)buildSequence:(NSMutableArray *)sequence treeIndex:(int)treeIndex;
- (void)findById:(NSString *)sourceId matches:(NSMutableArray<AudioSource *> *)matches;
- (NSArray *)getShuffleOrder;
- (int)shuffle:(int)treeIndex currentIndex:(int)currentIndex;
@end

View 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

View file

@ -0,0 +1,11 @@
#import "AudioSource.h"
#import "UriAudioSource.h"
#import <FlutterMacOS/FlutterMacOS.h>
@interface ClippingAudioSource : IndexedAudioSource
@property (readonly, nonatomic) UriAudioSource* audioSource;
- (instancetype)initWithId:(NSString *)sid audioSource:(UriAudioSource *)audioSource start:(NSNumber *)start end:(NSNumber *)end;
@end

View file

@ -0,0 +1,79 @@
#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;
}
- (UriAudioSource *)audioSource {
return _audioSource;
}
- (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 {
if (!completionHandler || (self.playerItem.status == AVPlayerItemStatusReadyToPlay)) {
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);
}
- (CMTime)bufferedPosition {
CMTime pos = CMTimeSubtract(_audioSource.bufferedPosition, _start);
CMTime dur = [self duration];
return CMTimeCompare(pos, dur) >= 0 ? dur : pos;
}
@end

View file

@ -0,0 +1,13 @@
#import "AudioSource.h"
#import <FlutterMacOS/FlutterMacOS.h>
@interface ConcatenatingAudioSource : AudioSource
@property (readonly, nonatomic) int count;
- (instancetype)initWithId:(NSString *)sid audioSources:(NSMutableArray<AudioSource *> *)audioSources;
- (void)insertSource:(AudioSource *)audioSource atIndex:(int)index;
- (void)removeSourcesFromIndex:(int)start toIndex:(int)end;
- (void)moveSourceFromIndex:(int)currentIndex toIndex:(int)newIndex;
@end

View 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

View file

@ -0,0 +1,21 @@
#import "AudioSource.h"
#import "IndexedPlayerItem.h"
#import <FlutterMacOS/FlutterMacOS.h>
#import <AVFoundation/AVFoundation.h>
@interface IndexedAudioSource : AudioSource
@property (readonly, nonatomic) IndexedPlayerItem *playerItem;
@property (readwrite, nonatomic) CMTime duration;
@property (readonly, nonatomic) CMTime position;
@property (readonly, nonatomic) CMTime bufferedPosition;
@property (readonly, nonatomic) BOOL isAttached;
- (void)attach:(AVQueuePlayer *)player;
- (void)play:(AVQueuePlayer *)player;
- (void)pause:(AVQueuePlayer *)player;
- (void)stop:(AVQueuePlayer *)player;
- (void)seek:(CMTime)position;
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler;
@end

View file

@ -0,0 +1,68 @@
#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;
}
- (CMTime)bufferedPosition {
return kCMTimeInvalid;
}
@end

View file

@ -0,0 +1,9 @@
#import <AVFoundation/AVFoundation.h>
@class IndexedAudioSource;
@interface IndexedPlayerItem : AVPlayerItem
@property (readwrite, nonatomic) IndexedAudioSource *audioSource;
@end

View 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

View file

@ -0,0 +1,4 @@
#import <FlutterMacOS/FlutterMacOS.h>
@interface JustAudioPlugin : NSObject<FlutterPlugin>
@end

View file

@ -0,0 +1,55 @@
#import "JustAudioPlugin.h"
#import "AudioPlayer.h"
#import <AVFoundation/AVFoundation.h>
#include <TargetConditionals.h>
@implementation JustAudioPlugin {
NSObject<FlutterPluginRegistrar>* _registrar;
BOOL _configuredSession;
}
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"com.ryanheise.just_audio.methods"
binaryMessenger:[registrar messenger]];
JustAudioPlugin* instance = [[JustAudioPlugin alloc] initWithRegistrar:registrar];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
self = [super init];
NSAssert(self, @"super init cannot be nil");
_registrar = registrar;
return self;
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([@"init" isEqualToString:call.method]) {
NSArray* args = (NSArray*)call.arguments;
NSString* playerId = args[0];
/*AudioPlayer* player =*/ [[AudioPlayer alloc] initWithRegistrar:_registrar playerId:playerId configuredSession:_configuredSession];
result(nil);
} else if ([@"setIosCategory" isEqualToString:call.method]) {
#if TARGET_OS_IPHONE
NSNumber* categoryIndex = (NSNumber*)call.arguments;
AVAudioSessionCategory category = nil;
switch (categoryIndex.integerValue) {
case 0: category = AVAudioSessionCategoryAmbient; break;
case 1: category = AVAudioSessionCategorySoloAmbient; break;
case 2: category = AVAudioSessionCategoryPlayback; break;
case 3: category = AVAudioSessionCategoryRecord; break;
case 4: category = AVAudioSessionCategoryPlayAndRecord; break;
case 5: category = AVAudioSessionCategoryMultiRoute; break;
}
if (category) {
_configuredSession = YES;
}
[[AVAudioSession sharedInstance] setCategory:category error:nil];
#endif
result(nil);
} else {
result(FlutterMethodNotImplemented);
}
}
@end

View file

@ -0,0 +1,8 @@
#import "AudioSource.h"
#import <FlutterMacOS/FlutterMacOS.h>
@interface LoopingAudioSource : AudioSource
- (instancetype)initWithId:(NSString *)sid audioSources:(NSArray<AudioSource *> *)audioSources;
@end

View 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 = (int)[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

View file

@ -0,0 +1,8 @@
#import "IndexedAudioSource.h"
#import <FlutterMacOS/FlutterMacOS.h>
@interface UriAudioSource : IndexedAudioSource
- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri;
@end

View file

@ -0,0 +1,79 @@
#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 {
if (!completionHandler || (_playerItem.status == AVPlayerItemStatusReadyToPlay)) {
[_playerItem seekToTime:position toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:completionHandler];
}
}
- (CMTime)duration {
return _playerItem.duration;
}
- (void)setDuration:(CMTime)duration {
}
- (CMTime)position {
return _playerItem.currentTime;
}
- (CMTime)bufferedPosition {
NSValue *last = _playerItem.loadedTimeRanges.lastObject;
if (last) {
CMTimeRange timeRange = [last CMTimeRangeValue];
return CMTimeAdd(timeRange.start, timeRange.duration);
} else {
return _playerItem.currentTime;
}
return kCMTimeInvalid;
}
@end