Gapless looping on iOS (#273)
This commit is contained in:
parent
a00286229e
commit
ff8e9ef81c
|
@ -40,6 +40,7 @@
|
||||||
BOOL _automaticallyWaitsToMinimizeStalling;
|
BOOL _automaticallyWaitsToMinimizeStalling;
|
||||||
BOOL _playing;
|
BOOL _playing;
|
||||||
float _speed;
|
float _speed;
|
||||||
|
BOOL _justAdvanced;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam {
|
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar playerId:(NSString*)idParam {
|
||||||
|
@ -76,6 +77,7 @@
|
||||||
_playResult = nil;
|
_playResult = nil;
|
||||||
_automaticallyWaitsToMinimizeStalling = YES;
|
_automaticallyWaitsToMinimizeStalling = YES;
|
||||||
_speed = 1.0f;
|
_speed = 1.0f;
|
||||||
|
_justAdvanced = NO;
|
||||||
__weak __typeof__(self) weakSelf = self;
|
__weak __typeof__(self) weakSelf = self;
|
||||||
[_methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
|
[_methodChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
|
||||||
[weakSelf handleMethodCall:call result:result];
|
[weakSelf handleMethodCall:call result:result];
|
||||||
|
@ -208,6 +210,9 @@
|
||||||
IndexedAudioSource *audioSource = _indexedAudioSources[i];
|
IndexedAudioSource *audioSource = _indexedAudioSources[i];
|
||||||
while (audioSource != oldIndexedAudioSources[j]) {
|
while (audioSource != oldIndexedAudioSources[j]) {
|
||||||
[self removeItemObservers:oldIndexedAudioSources[j].playerItem];
|
[self removeItemObservers:oldIndexedAudioSources[j].playerItem];
|
||||||
|
if (oldIndexedAudioSources[j].playerItem2) {
|
||||||
|
[self removeItemObservers:oldIndexedAudioSources[j].playerItem2];
|
||||||
|
}
|
||||||
if (j < _index) {
|
if (j < _index) {
|
||||||
_index--;
|
_index--;
|
||||||
} else if (j == _index) {
|
} else if (j == _index) {
|
||||||
|
@ -282,12 +287,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)enterBuffering:(NSString *)reason {
|
- (void)enterBuffering:(NSString *)reason {
|
||||||
NSLog(@"ENTER BUFFERING: %@", reason);
|
//NSLog(@"ENTER BUFFERING: %@", reason);
|
||||||
_processingState = buffering;
|
_processingState = buffering;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)leaveBuffering:(NSString *)reason {
|
- (void)leaveBuffering:(NSString *)reason {
|
||||||
NSLog(@"LEAVE BUFFERING: %@", reason);
|
//NSLog(@"LEAVE BUFFERING: %@", reason);
|
||||||
_processingState = ready;
|
_processingState = ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,8 +375,8 @@
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onItemStalled:) name:AVPlayerItemPlaybackStalledNotification object:playerItem];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onItemStalled:) name:AVPlayerItemPlaybackStalledNotification object:playerItem];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSMutableArray *)decodeAudioSources:(NSArray *)data {
|
- (NSMutableArray<AudioSource *> *)decodeAudioSources:(NSArray *)data {
|
||||||
NSMutableArray *array = [[NSMutableArray alloc] init];
|
NSMutableArray<AudioSource *> *array = (NSMutableArray<AudioSource *> *)[[NSMutableArray alloc] init];
|
||||||
for (int i = 0; i < [data count]; i++) {
|
for (int i = 0; i < [data count]; i++) {
|
||||||
AudioSource *source = [self decodeAudioSource:data[i]];
|
AudioSource *source = [self decodeAudioSource:data[i]];
|
||||||
[array addObject:source];
|
[array addObject:source];
|
||||||
|
@ -409,6 +414,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)enqueueFrom:(int)index {
|
- (void)enqueueFrom:(int)index {
|
||||||
|
//NSLog(@"### enqueueFrom:%d", index);
|
||||||
_index = index;
|
_index = index;
|
||||||
|
|
||||||
// Update the queue while keeping the currently playing item untouched.
|
// Update the queue while keeping the currently playing item untouched.
|
||||||
|
@ -447,6 +453,7 @@
|
||||||
/* [self dumpQueue]; */
|
/* [self dumpQueue]; */
|
||||||
|
|
||||||
// Regenerate queue
|
// Regenerate queue
|
||||||
|
if (!existingItem || _loopMode != loopOne) {
|
||||||
BOOL include = NO;
|
BOOL include = NO;
|
||||||
for (int i = 0; i < [_order count]; i++) {
|
for (int i = 0; i < [_order count]; i++) {
|
||||||
int si = [_order[i] intValue];
|
int si = [_order[i] intValue];
|
||||||
|
@ -454,8 +461,31 @@
|
||||||
if (include && _indexedAudioSources[si].playerItem != existingItem) {
|
if (include && _indexedAudioSources[si].playerItem != existingItem) {
|
||||||
//NSLog(@"inserting item %d", si);
|
//NSLog(@"inserting item %d", si);
|
||||||
[_player insertItem:_indexedAudioSources[si].playerItem afterItem:nil];
|
[_player insertItem:_indexedAudioSources[si].playerItem afterItem:nil];
|
||||||
|
if (_loopMode == loopOne) {
|
||||||
|
// We only want one item in the queue.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add next loop item if we're looping
|
||||||
|
if (_loopMode == loopAll) {
|
||||||
|
int si = [_order[0] intValue];
|
||||||
|
//NSLog(@"### add loop item:%d", si);
|
||||||
|
if (!_indexedAudioSources[si].playerItem2) {
|
||||||
|
[_indexedAudioSources[si] preparePlayerItem2];
|
||||||
|
[self addItemObservers:_indexedAudioSources[si].playerItem2];
|
||||||
|
}
|
||||||
|
[_player insertItem:_indexedAudioSources[si].playerItem2 afterItem:nil];
|
||||||
|
} else if (_loopMode == loopOne) {
|
||||||
|
//NSLog(@"### add loop item:%d", _index);
|
||||||
|
if (!_indexedAudioSources[_index].playerItem2) {
|
||||||
|
[_indexedAudioSources[_index] preparePlayerItem2];
|
||||||
|
[self addItemObservers:_indexedAudioSources[_index].playerItem2];
|
||||||
|
}
|
||||||
|
[_player insertItem:_indexedAudioSources[_index].playerItem2 afterItem:nil];
|
||||||
|
}
|
||||||
|
|
||||||
/* NSLog(@"after reorder: _player.items.count: ", _player.items.count); */
|
/* NSLog(@"after reorder: _player.items.count: ", _player.items.count); */
|
||||||
/* [self dumpQueue]; */
|
/* [self dumpQueue]; */
|
||||||
|
@ -495,6 +525,9 @@
|
||||||
if (_indexedAudioSources) {
|
if (_indexedAudioSources) {
|
||||||
for (int i = 0; i < [_indexedAudioSources count]; i++) {
|
for (int i = 0; i < [_indexedAudioSources count]; i++) {
|
||||||
[self removeItemObservers:_indexedAudioSources[i].playerItem];
|
[self removeItemObservers:_indexedAudioSources[i].playerItem];
|
||||||
|
if (_indexedAudioSources[i].playerItem2) {
|
||||||
|
[self removeItemObservers:_indexedAudioSources[i].playerItem2];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Decode audio source
|
// Decode audio source
|
||||||
|
@ -572,6 +605,10 @@
|
||||||
_player.rate = _speed;
|
_player.rate = _speed;
|
||||||
}
|
}
|
||||||
[self broadcastPlaybackEvent];
|
[self broadcastPlaybackEvent];
|
||||||
|
/* NSLog(@"load:"); */
|
||||||
|
/* for (int i = 0; i < [_indexedAudioSources count]; i++) { */
|
||||||
|
/* NSLog(@"- %@", _indexedAudioSources[i].sourceId); */
|
||||||
|
/* } */
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateOrder {
|
- (void)updateOrder {
|
||||||
|
@ -605,57 +642,29 @@
|
||||||
|
|
||||||
- (void)onComplete:(NSNotification *)notification {
|
- (void)onComplete:(NSNotification *)notification {
|
||||||
NSLog(@"onComplete");
|
NSLog(@"onComplete");
|
||||||
if (_loopMode == loopOne) {
|
|
||||||
__weak __typeof__(self) weakSelf = self;
|
|
||||||
[self seek:kCMTimeZero index:@(_index) completionHandler:^(BOOL finished) {
|
|
||||||
// XXX: Not necessary?
|
|
||||||
[weakSelf play];
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
IndexedPlayerItem *endedPlayerItem = (IndexedPlayerItem *)notification.object;
|
IndexedPlayerItem *endedPlayerItem = (IndexedPlayerItem *)notification.object;
|
||||||
IndexedAudioSource *endedSource = endedPlayerItem.audioSource;
|
IndexedAudioSource *endedSource = endedPlayerItem.audioSource;
|
||||||
|
|
||||||
if ([_orderInv[_index] intValue] + 1 < [_order count]) {
|
if (_loopMode == loopOne) {
|
||||||
// When an item ends, seek back to its beginning.
|
|
||||||
[endedSource seek:kCMTimeZero];
|
[endedSource seek:kCMTimeZero];
|
||||||
// account for automatic move to next item
|
_justAdvanced = YES;
|
||||||
_index = [_order[[_orderInv[_index] intValue] + 1] intValue];
|
} else if (_loopMode == loopAll) {
|
||||||
NSLog(@"advance to next: index = %d", _index);
|
[endedSource seek:kCMTimeZero];
|
||||||
|
_index = [_order[([_orderInv[_index] intValue] + 1) % _order.count] intValue];
|
||||||
|
[self broadcastPlaybackEvent];
|
||||||
|
_justAdvanced = YES;
|
||||||
|
} else if ([_orderInv[_index] intValue] + 1 < [_order count]) {
|
||||||
|
[endedSource seek:kCMTimeZero];
|
||||||
|
_index++;
|
||||||
[self updateEndAction];
|
[self updateEndAction];
|
||||||
[self broadcastPlaybackEvent];
|
[self broadcastPlaybackEvent];
|
||||||
|
_justAdvanced = YES;
|
||||||
} else {
|
} else {
|
||||||
// reached end of playlist
|
// reached end of playlist
|
||||||
if (_loopMode == loopAll) {
|
|
||||||
NSLog(@"Loop back to first item");
|
|
||||||
// Loop back to the beginning
|
|
||||||
// TODO: Currently there will be a gap at the loop point.
|
|
||||||
// Maybe we can do something clever by temporarily adding the
|
|
||||||
// first playlist item at the end of the queue, although this
|
|
||||||
// will affect any code that assumes the queue always
|
|
||||||
// corresponds to a contiguous region of the indexed audio
|
|
||||||
// sources.
|
|
||||||
// For now we just do a seek back to the start.
|
|
||||||
if ([_order count] == 1) {
|
|
||||||
__weak __typeof__(self) weakSelf = self;
|
|
||||||
[self seek:kCMTimeZero index:_order[0] completionHandler:^(BOOL finished) {
|
|
||||||
// XXX: Necessary?
|
|
||||||
[weakSelf play];
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
// When an item ends, seek back to its beginning.
|
|
||||||
[endedSource seek:kCMTimeZero];
|
|
||||||
__weak __typeof__(self) weakSelf = self;
|
|
||||||
[self seek:kCMTimeZero index:_order[0] completionHandler:^(BOOL finished) {
|
|
||||||
// XXX: Necessary?
|
|
||||||
[weakSelf play];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
[self complete];
|
[self complete];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||||
ofObject:(id)object
|
ofObject:(id)object
|
||||||
|
@ -790,7 +799,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ([keyPath isEqualToString:@"currentItem"] && _player.currentItem) {
|
} else if ([keyPath isEqualToString:@"currentItem"] && _player.currentItem) {
|
||||||
if (_player.currentItem.status == AVPlayerItemStatusFailed) {
|
IndexedPlayerItem *playerItem = (IndexedPlayerItem *)change[NSKeyValueChangeNewKey];
|
||||||
|
IndexedPlayerItem *oldPlayerItem = (IndexedPlayerItem *)change[NSKeyValueChangeOldKey];
|
||||||
|
if (playerItem.status == AVPlayerItemStatusFailed) {
|
||||||
if ([_orderInv[_index] intValue] + 1 < [_order count]) {
|
if ([_orderInv[_index] intValue] + 1 < [_order count]) {
|
||||||
// account for automatic move to next item
|
// account for automatic move to next item
|
||||||
_index = [_order[[_orderInv[_index] intValue] + 1] intValue];
|
_index = [_order[[_orderInv[_index] intValue] + 1] intValue];
|
||||||
|
@ -802,7 +813,7 @@
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
int expectedIndex = [self indexForItem:(IndexedPlayerItem *)_player.currentItem];
|
int expectedIndex = [self indexForItem:playerItem];
|
||||||
if (_index != expectedIndex) {
|
if (_index != expectedIndex) {
|
||||||
// AVQueuePlayer will sometimes skip over error items without
|
// AVQueuePlayer will sometimes skip over error items without
|
||||||
// notifying this observer.
|
// notifying this observer.
|
||||||
|
@ -816,9 +827,9 @@
|
||||||
_bufferUnconfirmed = YES;
|
_bufferUnconfirmed = YES;
|
||||||
// If we've skipped or transitioned to a new item and we're not
|
// If we've skipped or transitioned to a new item and we're not
|
||||||
// currently in the middle of a seek
|
// currently in the middle of a seek
|
||||||
/* if (CMTIME_IS_INVALID(_seekPos) && _player.currentItem.status == AVPlayerItemStatusReadyToPlay) { */
|
/* if (CMTIME_IS_INVALID(_seekPos) && playerItem.status == AVPlayerItemStatusReadyToPlay) { */
|
||||||
/* [self updatePosition]; */
|
/* [self updatePosition]; */
|
||||||
/* IndexedAudioSource *source = ((IndexedPlayerItem *)_player.currentItem).audioSource; */
|
/* IndexedAudioSource *source = playerItem.audioSource; */
|
||||||
/* // We should already be at position zero but for */
|
/* // We should already be at position zero but for */
|
||||||
/* // ClippingAudioSource it might be off by some milliseconds so we */
|
/* // ClippingAudioSource it might be off by some milliseconds so we */
|
||||||
/* // consider anything <= 100 as close enough. */
|
/* // consider anything <= 100 as close enough. */
|
||||||
|
@ -850,6 +861,22 @@
|
||||||
/* // Already at zero, no need to seek. */
|
/* // Already at zero, no need to seek. */
|
||||||
/* } */
|
/* } */
|
||||||
/* } */
|
/* } */
|
||||||
|
|
||||||
|
if (_justAdvanced) {
|
||||||
|
IndexedAudioSource *audioSource = playerItem.audioSource;
|
||||||
|
if (_loopMode == loopOne) {
|
||||||
|
[audioSource flip];
|
||||||
|
[self enqueueFrom:_index];
|
||||||
|
} else if (_loopMode == loopAll) {
|
||||||
|
if (_index == [_order[0] intValue] && playerItem == audioSource.playerItem2) {
|
||||||
|
[audioSource flip];
|
||||||
|
[self enqueueFrom:_index];
|
||||||
|
} else {
|
||||||
|
[self updateEndAction];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_justAdvanced = NO;
|
||||||
|
}
|
||||||
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
|
} else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
|
||||||
IndexedPlayerItem *playerItem = (IndexedPlayerItem *)object;
|
IndexedPlayerItem *playerItem = (IndexedPlayerItem *)object;
|
||||||
if (playerItem != _player.currentItem) return;
|
if (playerItem != _player.currentItem) return;
|
||||||
|
@ -889,7 +916,7 @@
|
||||||
|
|
||||||
- (int)indexForItem:(IndexedPlayerItem *)playerItem {
|
- (int)indexForItem:(IndexedPlayerItem *)playerItem {
|
||||||
for (int i = 0; i < _indexedAudioSources.count; i++) {
|
for (int i = 0; i < _indexedAudioSources.count; i++) {
|
||||||
if (_indexedAudioSources[i].playerItem == playerItem) {
|
if (_indexedAudioSources[i].playerItem == playerItem || _indexedAudioSources[i].playerItem2 == playerItem) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -989,14 +1016,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setLoopMode:(int)loopMode {
|
- (void)setLoopMode:(int)loopMode {
|
||||||
|
if (loopMode == _loopMode) return;
|
||||||
_loopMode = loopMode;
|
_loopMode = loopMode;
|
||||||
[self updateEndAction];
|
[self enqueueFrom:_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateEndAction {
|
- (void)updateEndAction {
|
||||||
// Should update this whenever the audio source changes and whenever _index changes.
|
// Should be called in the following situations:
|
||||||
|
// - when the audio source changes
|
||||||
|
// - when _index changes
|
||||||
|
// - when the loop mode changes.
|
||||||
|
// - when the shuffle order changes. (TODO)
|
||||||
|
// - when the shuffle mode changes.
|
||||||
if (!_player) return;
|
if (!_player) return;
|
||||||
if (_audioSource && [_orderInv[_index] intValue] + 1 < [_order count] && _loopMode != loopOne) {
|
if (_audioSource && (_loopMode != loopOff || [_orderInv[_index] intValue] + 1 < [_order count])) {
|
||||||
_player.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
|
_player.actionAtItemEnd = AVPlayerActionAtItemEndAdvance;
|
||||||
} else {
|
} else {
|
||||||
_player.actionAtItemEnd = AVPlayerActionAtItemEndPause; // AVPlayerActionAtItemEndNone
|
_player.actionAtItemEnd = AVPlayerActionAtItemEndPause; // AVPlayerActionAtItemEndNone
|
||||||
|
@ -1014,19 +1047,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setShuffleOrder:(NSDictionary *)dict {
|
- (void)setShuffleOrder:(NSDictionary *)dict {
|
||||||
|
// TODO: update order and enqueue.
|
||||||
[_audioSource decodeShuffleOrder:dict];
|
[_audioSource decodeShuffleOrder:dict];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dumpQueue {
|
- (void)dumpQueue {
|
||||||
for (int i = 0; i < _player.items.count; i++) {
|
for (int i = 0; i < _player.items.count; i++) {
|
||||||
IndexedPlayerItem *playerItem = (IndexedPlayerItem *)_player.items[i];
|
IndexedPlayerItem *playerItem = (IndexedPlayerItem *)_player.items[i];
|
||||||
for (int j = 0; j < _indexedAudioSources.count; j++) {
|
int j = [self indexForItem:playerItem];
|
||||||
IndexedAudioSource *source = _indexedAudioSources[j];
|
|
||||||
if (source.playerItem == playerItem) {
|
|
||||||
NSLog(@"- %d", j);
|
NSLog(@"- %d", j);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1189,6 +1218,9 @@
|
||||||
if (_indexedAudioSources) {
|
if (_indexedAudioSources) {
|
||||||
for (int i = 0; i < [_indexedAudioSources count]; i++) {
|
for (int i = 0; i < [_indexedAudioSources count]; i++) {
|
||||||
[self removeItemObservers:_indexedAudioSources[i].playerItem];
|
[self removeItemObservers:_indexedAudioSources[i].playerItem];
|
||||||
|
if (_indexedAudioSources[i].playerItem2) {
|
||||||
|
[self removeItemObservers:_indexedAudioSources[i].playerItem2];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_indexedAudioSources = nil;
|
_indexedAudioSources = nil;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@
|
||||||
|
|
||||||
- (void)attach:(AVQueuePlayer *)player {
|
- (void)attach:(AVQueuePlayer *)player {
|
||||||
[super attach:player];
|
[super attach:player];
|
||||||
|
// Prepare clip to start/end at the right timestamps.
|
||||||
_audioSource.playerItem.forwardPlaybackEndTime = _end;
|
_audioSource.playerItem.forwardPlaybackEndTime = _end;
|
||||||
// XXX: Not needed since currentItem observer handles it?
|
|
||||||
[self seek:kCMTimeZero];
|
[self seek:kCMTimeZero];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,10 @@
|
||||||
return _audioSource.playerItem;
|
return _audioSource.playerItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (IndexedPlayerItem *)playerItem2 {
|
||||||
|
return _audioSource.playerItem2;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSArray<NSNumber *> *)getShuffleIndices {
|
- (NSArray<NSNumber *> *)getShuffleIndices {
|
||||||
return @[@(0)];
|
return @[@(0)];
|
||||||
}
|
}
|
||||||
|
@ -61,6 +65,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)flip {
|
||||||
|
[_audioSource flip];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)preparePlayerItem2 {
|
||||||
|
if (self.playerItem2) return;
|
||||||
|
[_audioSource preparePlayerItem2];
|
||||||
|
IndexedPlayerItem *item = _audioSource.playerItem2;
|
||||||
|
// Prepare loop clip to start/end at the right timestamps.
|
||||||
|
item.forwardPlaybackEndTime = _end;
|
||||||
|
[item seekToTime:_start toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:nil];
|
||||||
|
}
|
||||||
|
|
||||||
- (CMTime)duration {
|
- (CMTime)duration {
|
||||||
return CMTimeSubtract(CMTIME_IS_INVALID(_end) ? self.playerItem.duration : _end, _start);
|
return CMTimeSubtract(CMTIME_IS_INVALID(_end) ? self.playerItem.duration : _end, _start);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithId:(NSString *)sid {
|
- (instancetype)initWithId:(NSString *)sid {
|
||||||
self = [super init];
|
self = [super initWithId:sid];
|
||||||
NSAssert(self, @"super init cannot be nil");
|
NSAssert(self, @"super init cannot be nil");
|
||||||
_isAttached = NO;
|
_isAttached = NO;
|
||||||
_queuedSeekPos = kCMTimeInvalid;
|
_queuedSeekPos = kCMTimeInvalid;
|
||||||
|
@ -33,6 +33,10 @@
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (IndexedPlayerItem *)playerItem2 {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)isAttached {
|
- (BOOL)isAttached {
|
||||||
return _isAttached;
|
return _isAttached;
|
||||||
}
|
}
|
||||||
|
@ -66,6 +70,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)flip {
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)preparePlayerItem2 {
|
||||||
|
}
|
||||||
|
|
||||||
- (CMTime)duration {
|
- (CMTime)duration {
|
||||||
return kCMTimeInvalid;
|
return kCMTimeInvalid;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
@implementation UriAudioSource {
|
@implementation UriAudioSource {
|
||||||
NSString *_uri;
|
NSString *_uri;
|
||||||
IndexedPlayerItem *_playerItem;
|
IndexedPlayerItem *_playerItem;
|
||||||
|
IndexedPlayerItem *_playerItem2;
|
||||||
/* CMTime _duration; */
|
/* CMTime _duration; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,28 +14,33 @@
|
||||||
self = [super initWithId:sid];
|
self = [super initWithId:sid];
|
||||||
NSAssert(self, @"super init cannot be nil");
|
NSAssert(self, @"super init cannot be nil");
|
||||||
_uri = uri;
|
_uri = uri;
|
||||||
if ([_uri hasPrefix:@"file://"]) {
|
_playerItem = [self createPlayerItem:uri];
|
||||||
_playerItem = [[IndexedPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[_uri stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] substringFromIndex:7]]];
|
_playerItem2 = nil;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IndexedPlayerItem *)createPlayerItem:(NSString *)uri {
|
||||||
|
IndexedPlayerItem *item;
|
||||||
|
if ([uri hasPrefix:@"file://"]) {
|
||||||
|
item = [[IndexedPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[uri stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] substringFromIndex:7]]];
|
||||||
} else {
|
} else {
|
||||||
_playerItem = [[IndexedPlayerItem alloc] initWithURL:[NSURL URLWithString:_uri]];
|
item = [[IndexedPlayerItem alloc] initWithURL:[NSURL URLWithString:uri]];
|
||||||
}
|
}
|
||||||
if (@available(macOS 10.13, iOS 11.0, *)) {
|
if (@available(macOS 10.13, iOS 11.0, *)) {
|
||||||
// This does the best at reducing distortion on voice with speeds below 1.0
|
// This does the best at reducing distortion on voice with speeds below 1.0
|
||||||
_playerItem.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmTimeDomain;
|
item.audioTimePitchAlgorithm = AVAudioTimePitchAlgorithmTimeDomain;
|
||||||
}
|
}
|
||||||
/* NSKeyValueObservingOptions options = */
|
return item;
|
||||||
/* NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew; */
|
|
||||||
/* [_playerItem addObserver:self */
|
|
||||||
/* forKeyPath:@"duration" */
|
|
||||||
/* options:options */
|
|
||||||
/* context:nil]; */
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IndexedPlayerItem *)playerItem {
|
- (IndexedPlayerItem *)playerItem {
|
||||||
return _playerItem;
|
return _playerItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (IndexedPlayerItem *)playerItem2 {
|
||||||
|
return _playerItem2;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSArray<NSNumber *> *)getShuffleIndices {
|
- (NSArray<NSNumber *> *)getShuffleIndices {
|
||||||
return @[@(0)];
|
return @[@(0)];
|
||||||
}
|
}
|
||||||
|
@ -61,6 +67,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)flip {
|
||||||
|
IndexedPlayerItem *temp = _playerItem;
|
||||||
|
_playerItem = _playerItem2;
|
||||||
|
_playerItem2 = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)preparePlayerItem2 {
|
||||||
|
if (!_playerItem2) {
|
||||||
|
_playerItem2 = [self createPlayerItem:_uri];
|
||||||
|
_playerItem2.audioSource = _playerItem.audioSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (CMTime)duration {
|
- (CMTime)duration {
|
||||||
NSValue *seekableRange = _playerItem.seekableTimeRanges.lastObject;
|
NSValue *seekableRange = _playerItem.seekableTimeRanges.lastObject;
|
||||||
if (seekableRange) {
|
if (seekableRange) {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
@interface IndexedAudioSource : AudioSource
|
@interface IndexedAudioSource : AudioSource
|
||||||
|
|
||||||
@property (readonly, nonatomic) IndexedPlayerItem *playerItem;
|
@property (readonly, nonatomic) IndexedPlayerItem *playerItem;
|
||||||
|
@property (readonly, nonatomic) IndexedPlayerItem *playerItem2;
|
||||||
@property (readwrite, nonatomic) CMTime duration;
|
@property (readwrite, nonatomic) CMTime duration;
|
||||||
@property (readonly, nonatomic) CMTime position;
|
@property (readonly, nonatomic) CMTime position;
|
||||||
@property (readonly, nonatomic) CMTime bufferedPosition;
|
@property (readonly, nonatomic) CMTime bufferedPosition;
|
||||||
|
@ -18,5 +19,7 @@
|
||||||
- (void)stop:(AVQueuePlayer *)player;
|
- (void)stop:(AVQueuePlayer *)player;
|
||||||
- (void)seek:(CMTime)position;
|
- (void)seek:(CMTime)position;
|
||||||
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler;
|
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler;
|
||||||
|
- (void)preparePlayerItem2;
|
||||||
|
- (void)flip;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
@interface IndexedAudioSource : AudioSource
|
@interface IndexedAudioSource : AudioSource
|
||||||
|
|
||||||
@property (readonly, nonatomic) IndexedPlayerItem *playerItem;
|
@property (readonly, nonatomic) IndexedPlayerItem *playerItem;
|
||||||
|
@property (readonly, nonatomic) IndexedPlayerItem *playerItem2;
|
||||||
@property (readwrite, nonatomic) CMTime duration;
|
@property (readwrite, nonatomic) CMTime duration;
|
||||||
@property (readonly, nonatomic) CMTime position;
|
@property (readonly, nonatomic) CMTime position;
|
||||||
@property (readonly, nonatomic) CMTime bufferedPosition;
|
@property (readonly, nonatomic) CMTime bufferedPosition;
|
||||||
|
@ -18,5 +19,7 @@
|
||||||
- (void)stop:(AVQueuePlayer *)player;
|
- (void)stop:(AVQueuePlayer *)player;
|
||||||
- (void)seek:(CMTime)position;
|
- (void)seek:(CMTime)position;
|
||||||
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler;
|
- (void)seek:(CMTime)position completionHandler:(void (^)(BOOL))completionHandler;
|
||||||
|
- (void)preparePlayerItem2;
|
||||||
|
- (void)flip;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
Loading…
Reference in New Issue