Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf13ba318f |
@@ -365,7 +365,7 @@
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../StreamingKit/StreamingKit",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -403,7 +403,7 @@
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../StreamingKit/StreamingKit",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
audioPlayer = [[STKAudioPlayer alloc] initWithOptions:(STKAudioPlayerOptions){ .flushQueueOnSeek = YES, .enableVolumeMixer = NO, .equalizerBandFrequencies = {50, 100, 200, 400, 800, 1600, 2600, 16000} }];
|
||||
audioPlayer.meteringEnabled = YES;
|
||||
audioPlayer.volume = 1;
|
||||
|
||||
|
||||
AudioPlayerView* audioPlayerView = [[AudioPlayerView alloc] initWithFrame:self.window.bounds andAudioPlayer:audioPlayer];
|
||||
|
||||
@@ -61,7 +60,7 @@
|
||||
|
||||
-(void) audioPlayerViewPlayFromHTTPSelected:(AudioPlayerView*)audioPlayerView
|
||||
{
|
||||
NSURL* url = [NSURL URLWithString:@"file:///Users/tum/Temp/airplane-cut.aac"];
|
||||
NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
|
||||
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];
|
||||
|
||||
@@ -90,7 +89,7 @@
|
||||
|
||||
-(void) audioPlayerViewQueuePcmWaveFileSelected:(AudioPlayerView*)audioPlayerView
|
||||
{
|
||||
NSURL* url = [NSURL URLWithString:@"http://www.abstractpath.com/files/audiosamples/perfectly.wav"];
|
||||
NSURL* url = [NSURL URLWithString:@"http://fs.bloom.fm/oss/audiosamples/perfectly.wav"];
|
||||
|
||||
STKDataSource* dataSource = [STKAudioPlayer dataSourceFromURL:url];
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
-(void) playFromHTTP
|
||||
{
|
||||
[audioPlayer play:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
|
||||
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
}
|
||||
|
||||
-(void) tick:(NSTimer*)timer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
StreamingKit (formally Audjustable) is an audio playback and streaming library for iOS and Mac OSX. StreamingKit uses CoreAudio to decompress and playback audio (using hardware or software codecs) whilst providing a clean and simple object-oriented API.
|
||||
|
||||
The primary motivation of this project was to decouple the input data sources from the actual player logic in order to allow advanced customizable input handling such as HTTP progressive download based streaming, encryption/decryption, auto-recovery, dynamic-buffering. StreamingKit is the only streaming and playback library that supports dead-easy [gapless playback](https://github.com/tumtumtum/StreamingKit/wiki/Gapless-playback) between audio files of differing formats.
|
||||
The primary motivation of this project was to decouple the input data sources from the actual player logic in order to allow advanced customizable input handling such as HTTP streaming, encryption/decryption, auto-recovery, dynamic-buffering. StreamingKit is the only streaming and playback library that supports dead-easy [gapless playback](https://github.com/tumtumtum/StreamingKit/wiki/Gapless-playback) between audio files of differing formats.
|
||||
|
||||
## Main Features
|
||||
|
||||
@@ -34,7 +34,7 @@ There are two main classes. The `STKDataSource` class which is the abstract bas
|
||||
```objective-c
|
||||
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
|
||||
|
||||
[audioPlayer play:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
|
||||
[audioPlayer play:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
```
|
||||
|
||||
### Gapless playback
|
||||
@@ -42,8 +42,8 @@ STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
|
||||
```objective-c
|
||||
STKAudioPlayer* audioPlayer = [[STKAudioPlayer alloc] init];
|
||||
|
||||
[audioPlayer queue:@"http://www.abstractpath.com/files/audiosamples/sample.mp3"];
|
||||
[audioPlayer queue:@"http://www.abstractpath.com/files/audiosamples/airplane.aac"];
|
||||
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/sample.mp3"];
|
||||
[audioPlayer queue:@"http://fs.bloom.fm/oss/audiosamples/airplane.aac"];
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "StreamingKit"
|
||||
s.version = "0.1.23"
|
||||
s.version = "0.1.19"
|
||||
s.summary = "A fast and extensible audio streamer for iOS and OSX with support for gapless playback and custom (non-HTTP) sources."
|
||||
s.homepage = "https://github.com/tumtumtum/StreamingKit/"
|
||||
s.license = 'MIT'
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
A1682FA318B3903900F29FEC /* STKBufferingDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = A1682FA218B3903900F29FEC /* STKBufferingDataSource.m */; };
|
||||
A168C6F118BB67DC003D170D /* STKBufferChunk.m in Sources */ = {isa = PBXBuildFile; fileRef = A168C6F018BB67DC003D170D /* STKBufferChunk.m */; };
|
||||
A1A4996B189E744400E2A2E2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1A4996A189E744400E2A2E2 /* Cocoa.framework */; };
|
||||
A1A49975189E744500E2A2E2 /* StreamingKitMac.m in Sources */ = {isa = PBXBuildFile; fileRef = A1A49974189E744500E2A2E2 /* StreamingKitMac.m */; };
|
||||
A1A4997B189E744500E2A2E2 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1E7C4D9188D57F60010896F /* XCTest.framework */; };
|
||||
@@ -83,6 +85,10 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
A1682FA118B3903900F29FEC /* STKBufferingDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKBufferingDataSource.h; sourceTree = "<group>"; };
|
||||
A1682FA218B3903900F29FEC /* STKBufferingDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKBufferingDataSource.m; sourceTree = "<group>"; };
|
||||
A168C6EF18BB67DC003D170D /* STKBufferChunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STKBufferChunk.h; sourceTree = "<group>"; };
|
||||
A168C6F018BB67DC003D170D /* STKBufferChunk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STKBufferChunk.m; sourceTree = "<group>"; };
|
||||
A1A49969189E744400E2A2E2 /* libStreamingKitMac.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStreamingKitMac.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A1A4996A189E744400E2A2E2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = Library/Frameworks/Cocoa.framework; sourceTree = DEVELOPER_DIR; };
|
||||
A1A4996D189E744500E2A2E2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||
@@ -266,6 +272,10 @@
|
||||
A1E7C4F2188D5E550010896F /* STKAudioPlayer.m */,
|
||||
A1E7C4F3188D5E550010896F /* STKAutoRecoveringHTTPDataSource.h */,
|
||||
A1E7C4F4188D5E550010896F /* STKAutoRecoveringHTTPDataSource.m */,
|
||||
A168C6EF18BB67DC003D170D /* STKBufferChunk.h */,
|
||||
A168C6F018BB67DC003D170D /* STKBufferChunk.m */,
|
||||
A1682FA118B3903900F29FEC /* STKBufferingDataSource.h */,
|
||||
A1682FA218B3903900F29FEC /* STKBufferingDataSource.m */,
|
||||
A1E7C4F5188D5E550010896F /* STKCoreFoundationDataSource.h */,
|
||||
A1E7C4F6188D5E550010896F /* STKCoreFoundationDataSource.m */,
|
||||
A1E7C4F7188D5E550010896F /* STKDataSource.h */,
|
||||
@@ -502,7 +512,9 @@
|
||||
A1BF65D2189A6582004DD08C /* STKQueueEntry.m in Sources */,
|
||||
A1E7C504188D5E550010896F /* STKHTTPDataSource.m in Sources */,
|
||||
A1E7C503188D5E550010896F /* STKDataSourceWrapper.m in Sources */,
|
||||
A1682FA318B3903900F29FEC /* STKBufferingDataSource.m in Sources */,
|
||||
A1E7C502188D5E550010896F /* STKDataSource.m in Sources */,
|
||||
A168C6F118BB67DC003D170D /* STKBufferChunk.m in Sources */,
|
||||
A1BF65D5189A65C6004DD08C /* NSMutableArray+STKAudioPlayer.m in Sources */,
|
||||
A1E7C500188D5E550010896F /* STKAutoRecoveringHTTPDataSource.m in Sources */,
|
||||
);
|
||||
@@ -662,7 +674,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
@@ -693,7 +705,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 4.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 6.0;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
|
||||
@@ -42,10 +42,6 @@
|
||||
#import "libkern/OSAtomic.h"
|
||||
#import <float.h>
|
||||
|
||||
#ifndef DBL_MAX
|
||||
#define DBL_MAX 1.7976931348623157e+308
|
||||
#endif
|
||||
|
||||
#pragma mark Defines
|
||||
|
||||
#define kOutputBus 0
|
||||
@@ -530,12 +526,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
{
|
||||
currentlyPlayingEntry.dataSource.delegate = nil;
|
||||
[currentlyReadingEntry.dataSource unregisterForEvents];
|
||||
|
||||
OSSpinLockLock(¤tEntryReferencesLock);
|
||||
|
||||
currentlyPlayingEntry = nil;
|
||||
|
||||
OSSpinLockUnlock(¤tEntryReferencesLock);
|
||||
}
|
||||
|
||||
[self stopAudioUnitWithReason:STKAudioPlayerStopReasonDisposed];
|
||||
@@ -805,11 +796,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
pthread_mutex_lock(&playerMutex);
|
||||
|
||||
if (entryToUpdate->audioStreamBasicDescription.mFormatID == 0)
|
||||
{
|
||||
entryToUpdate->audioStreamBasicDescription = newBasicDescription;
|
||||
}
|
||||
|
||||
entryToUpdate->audioStreamBasicDescription = newBasicDescription;
|
||||
entryToUpdate->sampleRate = entryToUpdate->audioStreamBasicDescription.mSampleRate;
|
||||
entryToUpdate->packetDuration = entryToUpdate->audioStreamBasicDescription.mFramesPerPacket / entryToUpdate->sampleRate;
|
||||
|
||||
@@ -890,8 +877,13 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE || pasbd.mFormatID == kAudioFormatMPEG4AAC_HE_V2)
|
||||
{
|
||||
//
|
||||
// We've found HE-AAC, remember this to tell the audio queue
|
||||
// when we construct it.
|
||||
//
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
currentlyReadingEntry->audioStreamBasicDescription = pasbd;
|
||||
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -932,15 +924,20 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
}
|
||||
|
||||
OSSpinLockLock(¤tEntryReferencesLock);
|
||||
|
||||
STKQueueEntry* entry = currentlyPlayingEntry;
|
||||
OSSpinLockUnlock(¤tEntryReferencesLock);
|
||||
|
||||
if (entry == nil)
|
||||
{
|
||||
return 0;
|
||||
OSSpinLockUnlock(¤tEntryReferencesLock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
double retval = [entry duration];
|
||||
|
||||
OSSpinLockUnlock(¤tEntryReferencesLock);
|
||||
|
||||
double progress = [self progress];
|
||||
|
||||
if (retval < progress && retval > 0)
|
||||
@@ -963,9 +960,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
return 0;
|
||||
}
|
||||
|
||||
OSSpinLockLock(¤tEntryReferencesLock);
|
||||
STKQueueEntry* entry = currentlyPlayingEntry;
|
||||
OSSpinLockUnlock(¤tEntryReferencesLock);
|
||||
|
||||
if (entry == nil)
|
||||
{
|
||||
@@ -1352,6 +1347,7 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&playerMutex);
|
||||
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -1402,8 +1398,6 @@ static void AudioFileStreamPacketsProc(void* clientData, UInt32 numberBytes, UIn
|
||||
|
||||
playbackThreadRunLoop = nil;
|
||||
|
||||
[self destroyAudioResources];
|
||||
|
||||
[threadFinishedCondLock lock];
|
||||
[threadFinishedCondLock unlockWithCondition:1];
|
||||
}
|
||||
@@ -2536,15 +2530,11 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
{
|
||||
STKAudioPlayer* audioPlayer = (__bridge STKAudioPlayer*)inRefCon;
|
||||
|
||||
OSSpinLockLock(&audioPlayer->currentEntryReferencesLock);
|
||||
STKQueueEntry* entry = audioPlayer->currentlyPlayingEntry;
|
||||
STKQueueEntry* currentlyReadingEntry = audioPlayer->currentlyReadingEntry;
|
||||
OSSpinLockUnlock(&audioPlayer->currentEntryReferencesLock);
|
||||
|
||||
OSSpinLockLock(&audioPlayer->pcmBufferSpinLock);
|
||||
|
||||
BOOL waitForBuffer = NO;
|
||||
BOOL muted = audioPlayer->muted;
|
||||
STKQueueEntry* entry = audioPlayer->currentlyPlayingEntry;
|
||||
AudioBuffer* audioBuffer = audioPlayer->pcmAudioBuffer;
|
||||
UInt32 frameSizeInBytes = audioPlayer->pcmBufferFrameSizeInBytes;
|
||||
UInt32 used = audioPlayer->pcmBufferUsedFrameCount;
|
||||
@@ -2562,10 +2552,10 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
|
||||
if (entry->lastFrameQueued >= 0)
|
||||
{
|
||||
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, entry->lastFrameQueued);
|
||||
framesRequiredToStartPlaying = MIN(framesRequiredToStartPlaying, audioPlayer->currentlyPlayingEntry->lastFrameQueued);
|
||||
}
|
||||
|
||||
if (entry && currentlyReadingEntry == entry
|
||||
if (entry && audioPlayer->currentlyReadingEntry == entry
|
||||
&& entry->framesQueued < framesRequiredToStartPlaying)
|
||||
{
|
||||
waitForBuffer = YES;
|
||||
@@ -2775,19 +2765,13 @@ static OSStatus OutputRenderCallback(void* inRefCon, AudioUnitRenderActionFlags*
|
||||
{
|
||||
pthread_mutex_lock(&audioPlayer->playerMutex);
|
||||
|
||||
OSSpinLockLock(&audioPlayer->currentEntryReferencesLock);
|
||||
STKQueueEntry* currentlyPlayingEntry = audioPlayer->currentlyPlayingEntry;
|
||||
OSSpinLockUnlock(&audioPlayer->currentEntryReferencesLock);
|
||||
|
||||
if (lastFramePlayed && entry == currentlyPlayingEntry)
|
||||
if (lastFramePlayed && entry == audioPlayer->currentlyPlayingEntry)
|
||||
{
|
||||
[audioPlayer audioQueueFinishedPlaying:entry];
|
||||
|
||||
while (extraFramesPlayedNotAssigned > 0)
|
||||
{
|
||||
OSSpinLockLock(&audioPlayer->currentEntryReferencesLock);
|
||||
STKQueueEntry* newEntry = audioPlayer->currentlyPlayingEntry;
|
||||
OSSpinLockUnlock(&audioPlayer->currentEntryReferencesLock);
|
||||
|
||||
if (newEntry != nil)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// STKBufferChunk.h
|
||||
// StreamingKit
|
||||
//
|
||||
// Created by Thong Nguyen on 24/02/2014.
|
||||
// Copyright (c) 2014 Thong Nguyen. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface STKBufferChunk : NSObject
|
||||
{
|
||||
@public
|
||||
UInt32 index;
|
||||
UInt32 size;
|
||||
UInt32 position;
|
||||
UInt8* buffer;
|
||||
}
|
||||
|
||||
@property (readonly) UInt32 absoluteStart;
|
||||
@property (readonly) UInt32 absolutePosition;
|
||||
|
||||
-(id) initWithBufferSize:(UInt32)sizeIn;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// STKBufferChunk.m
|
||||
// StreamingKit
|
||||
//
|
||||
// Created by Thong Nguyen on 24/02/2014.
|
||||
// Copyright (c) 2014 Thong Nguyen. All rights reserved.
|
||||
//
|
||||
|
||||
#import "STKBufferChunk.h"
|
||||
|
||||
@implementation STKBufferChunk
|
||||
|
||||
-(id) initWithBufferSize:(UInt32)sizeIn
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
self->size = sizeIn;
|
||||
|
||||
self->buffer = calloc(sizeof(UInt8), sizeIn);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
free(self->buffer);
|
||||
}
|
||||
|
||||
-(UInt32) absoluteStart
|
||||
{
|
||||
return self->index * self->size;
|
||||
}
|
||||
|
||||
-(UInt32) absolutePosition
|
||||
{
|
||||
return self.absoluteStart + self->position;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/**********************************************************************************
|
||||
STKBufferingDataSource.h
|
||||
|
||||
Created by Thong Nguyen on 16/10/2012.
|
||||
https://github.com/tumtumtum/audjustable
|
||||
|
||||
Copyright (c) 2012-2014 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. All advertising materials mentioning features or use of this software
|
||||
must display the following acknowledgement:
|
||||
This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
|
||||
4. Neither the name of Thong Nguyen nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THONG NGUYEN BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**********************************************************************************/
|
||||
|
||||
#import "STKDataSource.h"
|
||||
|
||||
@interface STKBufferingDataSource : STKDataSource
|
||||
|
||||
@property (readonly) SInt64 position;
|
||||
@property (readonly) SInt64 length;
|
||||
|
||||
-(id) initWithDataSource:(STKDataSource*)dataSourceIn withMaxSize:(int)maxSizeIn;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,319 @@
|
||||
/**********************************************************************************
|
||||
STKBufferingDataSource.m
|
||||
|
||||
Created by Thong Nguyen on 16/10/2012.
|
||||
https://github.com/tumtumtum/audjustable
|
||||
|
||||
Copyright (c) 2012-2014 Thong Nguyen (tumtumtum@gmail.com). All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. All advertising materials mentioning features or use of this software
|
||||
must display the following acknowledgement:
|
||||
This product includes software developed by Thong Nguyen (tumtumtum@gmail.com)
|
||||
4. Neither the name of Thong Nguyen nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Thong Nguyen ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THONG NGUYEN BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
**********************************************************************************/
|
||||
|
||||
#import "STKBufferingDataSource.h"
|
||||
#import "STKBufferChunk.h"
|
||||
#import <pthread.h>
|
||||
|
||||
#define STK_BUFFER_CHUNK_SIZE (128 * 1024)
|
||||
|
||||
@interface STKBufferingDataSource()
|
||||
{
|
||||
@private
|
||||
NSRunLoop* runLoop;
|
||||
SInt32 maxSize;
|
||||
UInt32 chunkSize;
|
||||
UInt32 chunkCount;
|
||||
SInt64 position;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t condition;
|
||||
STKBufferChunk* __strong * bufferChunks;
|
||||
STKDataSource* dataSource;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface STKBufferingDataSourceThread : NSThread
|
||||
{
|
||||
@private
|
||||
NSRunLoop* runLoop;
|
||||
NSConditionLock* threadStartedLock;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation STKBufferingDataSourceThread
|
||||
|
||||
-(id) init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
threadStartedLock = [[NSConditionLock alloc] initWithCondition:0];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(NSRunLoop*) runLoop
|
||||
{
|
||||
[threadStartedLock lockWhenCondition:1];
|
||||
[threadStartedLock unlockWithCondition:0];
|
||||
|
||||
return self->runLoop;
|
||||
}
|
||||
|
||||
-(void) main
|
||||
{
|
||||
runLoop = [NSRunLoop currentRunLoop];
|
||||
|
||||
[threadStartedLock lockWhenCondition:0];
|
||||
[threadStartedLock unlockWithCondition:1];
|
||||
|
||||
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
|
||||
|
||||
while (true)
|
||||
{
|
||||
NSDate* date = [[NSDate alloc] initWithTimeIntervalSinceNow:10];
|
||||
|
||||
[runLoop runMode:NSDefaultRunLoopMode beforeDate:date];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static STKBufferingDataSourceThread* thread;
|
||||
|
||||
@implementation STKBufferingDataSource
|
||||
|
||||
+(void) initialize
|
||||
{
|
||||
thread = [[STKBufferingDataSourceThread alloc] init];
|
||||
|
||||
[thread start];
|
||||
}
|
||||
|
||||
-(id) initWithDataSource:(STKDataSource*)dataSourceIn withMaxSize:(int)maxSizeIn
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
self->maxSize = maxSizeIn;
|
||||
self->dataSource = dataSourceIn;
|
||||
self->chunkSize = STK_BUFFER_CHUNK_SIZE;
|
||||
|
||||
self->dataSource.delegate = self.delegate;
|
||||
|
||||
[self->dataSource registerForEvents:[thread runLoop]];
|
||||
|
||||
pthread_mutexattr_t attr;
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
|
||||
pthread_mutex_init(&self->mutex, &attr);
|
||||
pthread_cond_init(&self->condition, NULL);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
self->dataSource.delegate = nil;
|
||||
|
||||
for (int i = 0; i < self->chunkCount; i++)
|
||||
{
|
||||
self->bufferChunks[i] = nil;
|
||||
}
|
||||
|
||||
free(self->bufferChunks);
|
||||
|
||||
pthread_mutex_destroy(&self->mutex);
|
||||
pthread_cond_destroy(&self->condition);
|
||||
}
|
||||
|
||||
-(void) createBuffer
|
||||
{
|
||||
if (self->bufferChunks == nil)
|
||||
{
|
||||
int length = (int)MIN(self.length == 0? 1024 * 1024 : self.length, self->maxSize);
|
||||
|
||||
self->chunkCount = (int)((length / self->chunkSize) + 1);
|
||||
self->bufferChunks = (__strong STKBufferChunk**)calloc(sizeof(STKBufferChunk*), self->chunkCount);
|
||||
}
|
||||
}
|
||||
|
||||
-(STKBufferChunk*) chunkForPosition:(SInt64)positionIn createIfNotExist:(BOOL)createIfNotExist
|
||||
{
|
||||
int chunkIndex = (int)(positionIn / chunkCount);
|
||||
|
||||
if (self->bufferChunks[chunkIndex] == nil && createIfNotExist)
|
||||
{
|
||||
self->bufferChunks[chunkIndex] = [[STKBufferChunk alloc] initWithBufferSize:STK_BUFFER_CHUNK_SIZE];
|
||||
}
|
||||
|
||||
return self->bufferChunks[chunkIndex];
|
||||
}
|
||||
|
||||
-(SInt64) length
|
||||
{
|
||||
return self->dataSource.length;
|
||||
}
|
||||
|
||||
-(void) seekToOffset:(SInt64)offset
|
||||
{
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
[self seekToNextGap];
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
-(BOOL) hasBytesAvailable
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(int) readIntoBuffer:(UInt8*)bufferIn withSize:(int)size
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
-(void) invokeBlockOnEventsRunLoop:(void(^)())block
|
||||
{
|
||||
if (!runLoop)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
block = [block copy];
|
||||
|
||||
CFRunLoopPerformBlock(runLoop.getCFRunLoop, NSRunLoopCommonModes, ^
|
||||
{
|
||||
if ([self hasBytesAvailable])
|
||||
{
|
||||
block();
|
||||
}
|
||||
});
|
||||
|
||||
CFRunLoopWakeUp(runLoop.getCFRunLoop);
|
||||
}
|
||||
|
||||
-(BOOL) registerForEvents:(NSRunLoop*)runLoopIn
|
||||
{
|
||||
runLoop = runLoopIn;
|
||||
|
||||
[dataSource registerForEvents:[thread runLoop]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(void) unregisterForEvents
|
||||
{
|
||||
runLoop = nil;
|
||||
|
||||
[dataSource unregisterForEvents];
|
||||
}
|
||||
|
||||
-(void) close
|
||||
{
|
||||
[dataSource unregisterForEvents];
|
||||
[dataSource close];
|
||||
}
|
||||
|
||||
-(void) seekToNextGap
|
||||
{
|
||||
int startChunkIndex = (int)(self->position / chunkCount);
|
||||
|
||||
for (int i = 0; i < self->chunkCount; i++)
|
||||
{
|
||||
int chunkIndex = (i + startChunkIndex) % self->chunkCount;
|
||||
|
||||
STKBufferChunk* chunk = self->bufferChunks[chunkIndex];
|
||||
|
||||
if (chunk == nil)
|
||||
{
|
||||
chunk = [[STKBufferChunk alloc] initWithBufferSize:STK_BUFFER_CHUNK_SIZE];
|
||||
|
||||
chunk->index = chunkIndex;
|
||||
|
||||
self->bufferChunks[chunkIndex] = chunk;
|
||||
}
|
||||
|
||||
if (chunk->position < chunk->size)
|
||||
{
|
||||
[dataSource seekToOffset:(self->chunkSize * chunk->index) + chunk->position];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void) dataSourceDataAvailable:(STKDataSource*)dataSourceIn
|
||||
{
|
||||
if (![dataSourceIn hasBytesAvailable])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
if (self->bufferChunks == nil)
|
||||
{
|
||||
[self createBuffer];
|
||||
}
|
||||
|
||||
SInt64 sourcePosition = dataSourceIn.position;
|
||||
|
||||
STKBufferChunk* chunk = [self chunkForPosition:sourcePosition createIfNotExist:YES];
|
||||
|
||||
if (chunk->position >= chunk->size)
|
||||
{
|
||||
[self seekToNextGap];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int offset = dataSourceIn.position % self->chunkSize;
|
||||
|
||||
if (offset >= chunk->position)
|
||||
{
|
||||
[self seekToNextGap];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesToRead = self->chunkSize - offset;
|
||||
int bytesRead = [dataSourceIn readIntoBuffer:(chunk->buffer + offset) withSize:bytesToRead];
|
||||
|
||||
chunk->position = offset + bytesRead;
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
-(void) dataSourceErrorOccured:(STKDataSource*)dataSourceIn
|
||||
{
|
||||
[self.delegate dataSourceErrorOccured:self];
|
||||
}
|
||||
|
||||
-(void) dataSourceEof:(STKDataSource*)dataSourceIn
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -47,7 +47,6 @@ typedef void(^STKAsyncURLProvider)(STKHTTPDataSource* dataSource, BOOL forSeek,
|
||||
|
||||
+(AudioFileTypeID) audioFileTypeHintFromMimeType:(NSString*)fileExtension;
|
||||
-(id) initWithURL:(NSURL*)url;
|
||||
-(id) initWithURL:(NSURL *)url httpRequestHeaders:(NSDictionary *)httpRequestHeaders;
|
||||
-(id) initWithURLProvider:(STKURLProvider)urlProvider;
|
||||
-(id) initWithAsyncURLProvider:(STKAsyncURLProvider)asyncUrlProvider;
|
||||
-(NSRunLoop*) eventsRunLoop;
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
STKAsyncURLProvider asyncUrlProvider;
|
||||
NSDictionary* httpHeaders;
|
||||
AudioFileTypeID audioFileTypeHint;
|
||||
NSDictionary* requestHeaders;
|
||||
}
|
||||
-(void) open;
|
||||
|
||||
@@ -62,13 +61,6 @@
|
||||
return [self initWithURLProvider:^NSURL* { return urlIn; }];
|
||||
}
|
||||
|
||||
-(id) initWithURL:(NSURL *)urlIn httpRequestHeaders:(NSDictionary *)httpRequestHeaders
|
||||
{
|
||||
self = [self initWithURLProvider:^NSURL* { return urlIn; }];
|
||||
self->requestHeaders = httpRequestHeaders;
|
||||
return self;
|
||||
}
|
||||
|
||||
-(id) initWithURLProvider:(STKURLProvider)urlProviderIn
|
||||
{
|
||||
urlProviderIn = [urlProviderIn copy];
|
||||
@@ -307,12 +299,6 @@
|
||||
discontinuous = YES;
|
||||
}
|
||||
|
||||
for (NSString* key in self->requestHeaders)
|
||||
{
|
||||
NSString* value = [self->requestHeaders objectForKey:key];
|
||||
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)value);
|
||||
}
|
||||
|
||||
stream = CFReadStreamCreateForHTTPRequest(NULL, message);
|
||||
|
||||
if (stream == nil)
|
||||
|
||||
@@ -200,18 +200,6 @@
|
||||
[self open];
|
||||
}
|
||||
|
||||
if (stream == 0)
|
||||
{
|
||||
CFRunLoopPerformBlock(eventsRunLoop.getCFRunLoop, NSRunLoopCommonModes, ^
|
||||
{
|
||||
[self errorOccured];
|
||||
});
|
||||
|
||||
CFRunLoopWakeUp(eventsRunLoop.getCFRunLoop);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CFReadStreamSetProperty(stream, kCFStreamPropertyFileCurrentOffset, (__bridge CFTypeRef)[NSNumber numberWithLongLong:offset]) != TRUE)
|
||||
{
|
||||
position = 0;
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
self->spinLock = OS_SPINLOCK_INIT;
|
||||
|
||||
self.dataSource = dataSourceIn;
|
||||
self.queueItemId = queueItemIdIn;
|
||||
self->lastFrameQueued = -1;
|
||||
|
||||
Reference in New Issue
Block a user