diff --git a/DOUAudioStreamer.podspec b/DOUAudioStreamer.podspec deleted file mode 100644 index 8e59b12..0000000 --- a/DOUAudioStreamer.podspec +++ /dev/null @@ -1,17 +0,0 @@ -Pod::Spec.new do |s| - s.name = "DOUAudioStreamer" - s.version = "0.2.15" - s.license = { :type => "BSD", :file => "LICENSE" } - s.summary = "A Core Audio based streaming audio player for iOS/Mac." - s.homepage = "https://github.com/douban/DOUAudioStreamer" - s.author = { "Chongyu Zhu" => "i@lembacon.com" } - s.source = { :git => "https://github.com/douban/DOUAudioStreamer.git", :tag => s.version.to_s } - s.source_files = "src/*.{h,m}" - s.requires_arc = true - - s.ios.deployment_target = "5.0" - s.ios.frameworks = "Accelerate", "CFNetwork", "CoreAudio", "AudioToolbox", "AVFoundation", "MediaPlayer", "QuartzCore", "OpenGLES", "MobileCoreServices" - - s.osx.deployment_target = "10.7" - s.osx.framework = "Accelerate", "CFNetwork", "CoreAudio", "AudioToolbox", "AudioUnit", "CoreServices" -end diff --git a/DOUAudioStreamer_CodeEagle.podspec b/DOUAudioStreamer_CodeEagle.podspec new file mode 100644 index 0000000..bc29593 --- /dev/null +++ b/DOUAudioStreamer_CodeEagle.podspec @@ -0,0 +1,31 @@ +Pod::Spec.new do |s| + # DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac: + # + # http://github.com/douban/DOUAudioStreamer + # + # Copyright 2013-2014 Douban Inc. All rights reserved. + # + # Use and distribution licensed under the BSD license. See + # the LICENSE file for full text. + # + # Authors: + # Chongyu Zhu + # + + + s.name = "DOUAudioStreamer_CodeEagle" + s.version = "0.2.15" + s.license = { :type => "BSD", :file => "LICENSE" } + s.summary = "A Core Audio based streaming audio player for iOS/Mac. CodeEagle Modified Version" + s.homepage = "https://github.com/CodeEagle/DOUAudioStreamer" + s.author = { "CodeEagle" => "stasura@hotmail.com" } + s.source = { :git => "https://github.com/CodeEagle/DOUAudioStreamer.git", :tag => s.version.to_s } + s.source_files = "src/*.{h,m}" + s.requires_arc = true + + s.ios.deployment_target = "5.0" + s.ios.frameworks = "Accelerate", "CFNetwork", "CoreAudio", "AudioToolbox", "AVFoundation", "MediaPlayer", "QuartzCore", "OpenGLES", "MobileCoreServices" + + s.osx.deployment_target = "10.7" + s.osx.framework = "Accelerate", "CFNetwork", "CoreAudio", "AudioToolbox", "AudioUnit", "CoreServices" +end diff --git a/example/DOUASDemo/DOUASDemo.xcodeproj/project.pbxproj b/example/DOUASDemo/DOUASDemo.xcodeproj/project.pbxproj index cdcbfa7..b1ae7ff 100644 --- a/example/DOUASDemo/DOUASDemo.xcodeproj/project.pbxproj +++ b/example/DOUASDemo/DOUASDemo.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 16F36D1E1BCCE1AB004F08C5 /* DOUCacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 16F36D1B1BCCE1AB004F08C5 /* DOUCacheManager.m */; settings = {ASSET_TAGS = (); }; }; + 16F36D1F1BCCE1AB004F08C5 /* DOUTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = 16F36D1D1BCCE1AB004F08C5 /* DOUTrack.m */; settings = {ASSET_TAGS = (); }; }; D400E947171BF13F00BE7F37 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D400E946171BF13F00BE7F37 /* Accelerate.framework */; }; D40A255D17694680000B98AA /* DOUAudioAnalyzer+Default.m in Sources */ = {isa = PBXBuildFile; fileRef = D40A255C17694680000B98AA /* DOUAudioAnalyzer+Default.m */; }; D40A4D1717AB83C400D0305F /* DOUAudioStreamer+Options.m in Sources */ = {isa = PBXBuildFile; fileRef = D40A4D1617AB83C400D0305F /* DOUAudioStreamer+Options.m */; }; @@ -50,6 +52,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 16F36D1A1BCCE1AB004F08C5 /* DOUCacheManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOUCacheManager.h; sourceTree = ""; }; + 16F36D1B1BCCE1AB004F08C5 /* DOUCacheManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DOUCacheManager.m; sourceTree = ""; }; + 16F36D1C1BCCE1AB004F08C5 /* DOUTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOUTrack.h; sourceTree = ""; }; + 16F36D1D1BCCE1AB004F08C5 /* DOUTrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DOUTrack.m; sourceTree = ""; }; D400E946171BF13F00BE7F37 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; D40A255B17694680000B98AA /* DOUAudioAnalyzer+Default.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "DOUAudioAnalyzer+Default.h"; sourceTree = ""; }; D40A255C17694680000B98AA /* DOUAudioAnalyzer+Default.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "DOUAudioAnalyzer+Default.m"; sourceTree = ""; }; @@ -148,6 +154,11 @@ D43ACD731738B47B00E6A571 /* DOUAudioStreamer */ = { isa = PBXGroup; children = ( + D47CA39F17680ADA002D73B4 /* DOUAudioAnalyzer_Private.h */, + D47CA3A017680ADA002D73B4 /* DOUAudioAnalyzer.h */, + D47CA3A117680ADA002D73B4 /* DOUAudioAnalyzer.m */, + D40A255B17694680000B98AA /* DOUAudioAnalyzer+Default.h */, + D40A255C17694680000B98AA /* DOUAudioAnalyzer+Default.m */, D47CA39E17680AC5002D73B4 /* DOUAudioBase.h */, D43ACD741738B47B00E6A571 /* DOUAudioDecoder.h */, D43ACD751738B47B00E6A571 /* DOUAudioDecoder.m */, @@ -158,36 +169,35 @@ D43ACD7A1738B47B00E6A571 /* DOUAudioFilePreprocessor.m */, D43ACD7B1738B47B00E6A571 /* DOUAudioFileProvider.h */, D43ACD7C1738B47B00E6A571 /* DOUAudioFileProvider.m */, + D47CA3A317680AF1002D73B4 /* DOUAudioFrequencyAnalyzer.h */, + D47CA3A417680AF1002D73B4 /* DOUAudioFrequencyAnalyzer.m */, D43ACD7D1738B47B00E6A571 /* DOUAudioLPCM.h */, D43ACD7E1738B47B00E6A571 /* DOUAudioLPCM.m */, D43ACD7F1738B47B00E6A571 /* DOUAudioPlaybackItem.h */, D43ACD801738B47B00E6A571 /* DOUAudioPlaybackItem.m */, D43ACD811738B47B00E6A571 /* DOUAudioRenderer.h */, D43ACD821738B47B00E6A571 /* DOUAudioRenderer.m */, + D47CA3A517680AF1002D73B4 /* DOUAudioSpatialAnalyzer.h */, + D47CA3A617680AF1002D73B4 /* DOUAudioSpatialAnalyzer.m */, + D43ACD851738B47B00E6A571 /* DOUAudioStreamer_Private.h */, D43ACD831738B47B00E6A571 /* DOUAudioStreamer.h */, D43ACD841738B47B00E6A571 /* DOUAudioStreamer.m */, - D43ACD851738B47B00E6A571 /* DOUAudioStreamer_Private.h */, D40A4D1517AB83C400D0305F /* DOUAudioStreamer+Options.h */, D40A4D1617AB83C400D0305F /* DOUAudioStreamer+Options.m */, - D47CA3A017680ADA002D73B4 /* DOUAudioAnalyzer.h */, - D47CA3A117680ADA002D73B4 /* DOUAudioAnalyzer.m */, - D47CA39F17680ADA002D73B4 /* DOUAudioAnalyzer_Private.h */, - D40A255B17694680000B98AA /* DOUAudioAnalyzer+Default.h */, - D40A255C17694680000B98AA /* DOUAudioAnalyzer+Default.m */, - D47CA3A517680AF1002D73B4 /* DOUAudioSpatialAnalyzer.h */, - D47CA3A617680AF1002D73B4 /* DOUAudioSpatialAnalyzer.m */, - D47CA3A317680AF1002D73B4 /* DOUAudioFrequencyAnalyzer.h */, - D47CA3A417680AF1002D73B4 /* DOUAudioFrequencyAnalyzer.m */, - D43ACD861738B47B00E6A571 /* DOUSimpleHTTPRequest.h */, - D43ACD871738B47B00E6A571 /* DOUSimpleHTTPRequest.m */, + D43AFF91176A938100D1FECF /* DOUAudioVisualizer.h */, + D43AFF92176A938100D1FECF /* DOUAudioVisualizer.m */, + 16F36D1A1BCCE1AB004F08C5 /* DOUCacheManager.h */, + 16F36D1B1BCCE1AB004F08C5 /* DOUCacheManager.m */, + D43AFF95176A938100D1FECF /* DOUEAGLView.h */, + D43AFF96176A938100D1FECF /* DOUEAGLView.m */, D449209E18A5D12000651CD2 /* DOUMPMediaLibraryAssetLoader.h */, D449209F18A5D12000651CD2 /* DOUMPMediaLibraryAssetLoader.m */, + D43ACD861738B47B00E6A571 /* DOUSimpleHTTPRequest.h */, + D43ACD871738B47B00E6A571 /* DOUSimpleHTTPRequest.m */, + 16F36D1C1BCCE1AB004F08C5 /* DOUTrack.h */, + 16F36D1D1BCCE1AB004F08C5 /* DOUTrack.m */, D43ACD881738B47B00E6A571 /* NSData+DOUMappedFile.h */, D43ACD891738B47B00E6A571 /* NSData+DOUMappedFile.m */, - D43AFF95176A938100D1FECF /* DOUEAGLView.h */, - D43AFF96176A938100D1FECF /* DOUEAGLView.m */, - D43AFF91176A938100D1FECF /* DOUAudioVisualizer.h */, - D43AFF92176A938100D1FECF /* DOUAudioVisualizer.m */, ); name = DOUAudioStreamer; path = ../../../src; @@ -328,6 +338,7 @@ buildActionMask = 2147483647; files = ( D47CA3A717680AF1002D73B4 /* DOUAudioFrequencyAnalyzer.m in Sources */, + 16F36D1E1BCCE1AB004F08C5 /* DOUCacheManager.m in Sources */, D4C3892F171B9D87009D6124 /* main.m in Sources */, D40A4D1717AB83C400D0305F /* DOUAudioStreamer+Options.m in Sources */, D47CA3A817680AF1002D73B4 /* DOUAudioSpatialAnalyzer.m in Sources */, @@ -336,6 +347,7 @@ D43ACD8A1738B47B00E6A571 /* DOUAudioDecoder.m in Sources */, D43AFF97176A938100D1FECF /* DOUAudioVisualizer.m in Sources */, D43AFF99176A938100D1FECF /* DOUEAGLView.m in Sources */, + 16F36D1F1BCCE1AB004F08C5 /* DOUTrack.m in Sources */, D44920A018A5D12000651CD2 /* DOUMPMediaLibraryAssetLoader.m in Sources */, D43ACD8B1738B47B00E6A571 /* DOUAudioEventLoop.m in Sources */, D43ACD8C1738B47B00E6A571 /* DOUAudioFilePreprocessor.m in Sources */, diff --git a/src/DOUAudioFile.h b/src/DOUAudioFile.h index 56fdc55..23b41c5 100644 --- a/src/DOUAudioFile.h +++ b/src/DOUAudioFile.h @@ -26,7 +26,13 @@ @optional + +- (NSInteger )audioFileId; + +- (NSString *)audioFileName; + - (NSString *)audioFileHost; + - (DOUAudioFilePreprocessor *)audioFilePreprocessor; @end diff --git a/src/DOUAudioFileProvider.m b/src/DOUAudioFileProvider.m index 097a9b5..0b795a6 100644 --- a/src/DOUAudioFileProvider.m +++ b/src/DOUAudioFileProvider.m @@ -18,8 +18,10 @@ #import "DOUSimpleHTTPRequest.h" #import "NSData+DOUMappedFile.h" #import "DOUAudioStreamer+Options.h" +#import "DOUTrack.h" #include #include +#import "DOUCacheManager.h" #if TARGET_OS_IPHONE #include @@ -37,17 +39,17 @@ @interface DOUAudioFileProvider () { @protected - id _audioFile; - DOUAudioFileProviderEventBlock _eventBlock; - NSString *_cachedPath; - NSURL *_cachedURL; - NSString *_mimeType; - NSString *_fileExtension; - NSString *_sha256; - NSData *_mappedData; - NSUInteger _expectedLength; - NSUInteger _receivedLength; - BOOL _failed; + id _audioFile; + DOUAudioFileProviderEventBlock _eventBlock; + NSString *_cachedPath; + NSURL *_cachedURL; + NSString *_mimeType; + NSString *_fileExtension; + NSString *_sha256; + NSData *_mappedData; + NSUInteger _expectedLength; + NSUInteger _receivedLength; + BOOL _failed; } - (instancetype)_initWithAudioFile:(id )audioFile; @@ -59,6 +61,7 @@ @interface _DOUAudioLocalFileProvider : DOUAudioFileProvider @interface _DOUAudioRemoteFileProvider : DOUAudioFileProvider { @private + DOUSimpleHTTPRequest *_request; NSURL *_audioFileURL; NSString *_audioFileHost; @@ -69,14 +72,15 @@ @interface _DOUAudioRemoteFileProvider : DOUAudioFileProvider { BOOL _requiresCompleteFile; BOOL _readyToProducePackets; BOOL _requestCompleted; + } @end #if TARGET_OS_IPHONE @interface _DOUAudioMediaLibraryFileProvider : DOUAudioFileProvider { @private - DOUMPMediaLibraryAssetLoader *_assetLoader; - BOOL _loaderCompleted; + DOUMPMediaLibraryAssetLoader *_assetLoader; + BOOL _loaderCompleted; } @end #endif /* TARGET_OS_IPHONE */ @@ -87,81 +91,81 @@ @implementation _DOUAudioLocalFileProvider - (instancetype)_initWithAudioFile:(id )audioFile { - self = [super _initWithAudioFile:audioFile]; - if (self) { - _cachedURL = [audioFile audioFileURL]; - _cachedPath = [_cachedURL path]; - - BOOL isDirectory = NO; - if (![[NSFileManager defaultManager] fileExistsAtPath:_cachedPath - isDirectory:&isDirectory] || - isDirectory) { - return nil; + self = [super _initWithAudioFile:audioFile]; + if (self) { + _cachedURL = [audioFile audioFileURL]; + _cachedPath = [_cachedURL path]; + + BOOL isDirectory = NO; + if (![[NSFileManager defaultManager] fileExistsAtPath:_cachedPath + isDirectory:&isDirectory] || + isDirectory) { + return nil; + } + + _mappedData = [NSData dou_dataWithMappedContentsOfFile:_cachedPath]; + _expectedLength = [_mappedData length]; + _receivedLength = [_mappedData length]; } - - _mappedData = [NSData dou_dataWithMappedContentsOfFile:_cachedPath]; - _expectedLength = [_mappedData length]; - _receivedLength = [_mappedData length]; - } - - return self; + + return self; } - (NSString *)mimeType { - if (_mimeType == nil && - [self fileExtension] != nil) { - CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[self fileExtension], NULL); - if (uti != NULL) { - _mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); - CFRelease(uti); + if (_mimeType == nil && + [self fileExtension] != nil) { + CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[self fileExtension], NULL); + if (uti != NULL) { + _mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); + CFRelease(uti); + } } - } - - return _mimeType; + + return _mimeType; } - (NSString *)fileExtension { - if (_fileExtension == nil) { - _fileExtension = [[[self audioFile] audioFileURL] pathExtension]; - } - - return _fileExtension; + if (_fileExtension == nil) { + _fileExtension = [[[self audioFile] audioFileURL] pathExtension]; + } + + return _fileExtension; } - (NSString *)sha256 { - if (_sha256 == nil && - [DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256 && - [self mappedData] != nil) { - unsigned char hash[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256([[self mappedData] bytes], (CC_LONG)[[self mappedData] length], hash); - - NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; - for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { - [result appendFormat:@"%02x", hash[i]]; + if (_sha256 == nil && + [DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256 && + [self mappedData] != nil) { + unsigned char hash[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256([[self mappedData] bytes], (CC_LONG)[[self mappedData] length], hash); + + NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { + [result appendFormat:@"%02x", hash[i]]; + } + + _sha256 = [result copy]; } - - _sha256 = [result copy]; - } - - return _sha256; + + return _sha256; } - (NSUInteger)downloadSpeed { - return _receivedLength; + return _receivedLength; } - (BOOL)isReady { - return YES; + return YES; } - (BOOL)isFinished { - return YES; + return YES; } @end @@ -174,187 +178,191 @@ @implementation _DOUAudioRemoteFileProvider - (instancetype)_initWithAudioFile:(id )audioFile { - self = [super _initWithAudioFile:audioFile]; - if (self) { - _audioFileURL = [audioFile audioFileURL]; - if ([audioFile respondsToSelector:@selector(audioFileHost)]) { - _audioFileHost = [audioFile audioFileHost]; - } - - if ([DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256) { - _sha256Ctx = (CC_SHA256_CTX *)malloc(sizeof(CC_SHA256_CTX)); - CC_SHA256_Init(_sha256Ctx); + self = [super _initWithAudioFile:audioFile]; + if (self) { + _audioFileURL = [audioFile audioFileURL]; + if ([audioFile respondsToSelector:@selector(audioFileHost)]) { + _audioFileHost = [audioFile audioFileHost]; + } + + if ([DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256) { + _sha256Ctx = (CC_SHA256_CTX *)malloc(sizeof(CC_SHA256_CTX)); + CC_SHA256_Init(_sha256Ctx); + } + + [self _openAudioFileStream]; + [self _createRequest]; + [_request start]; } - - [self _openAudioFileStream]; - [self _createRequest]; - [_request start]; - } - - return self; + + return self; } + - (void)dealloc { - @synchronized(_request) { - [_request setCompletedBlock:NULL]; - [_request setProgressBlock:NULL]; - [_request setDidReceiveResponseBlock:NULL]; - [_request setDidReceiveDataBlock:NULL]; - - [_request cancel]; - } - - if (_sha256Ctx != NULL) { - free(_sha256Ctx); - } - - [self _closeAudioFileStream]; - - if ([DOUAudioStreamer options] & DOUAudioStreamerRemoveCacheOnDeallocation) { - [[NSFileManager defaultManager] removeItemAtPath:_cachedPath error:NULL]; - } + @synchronized(_request) { + [_request setCompletedBlock:NULL]; + [_request setProgressBlock:NULL]; + [_request setDidReceiveResponseBlock:NULL]; + [_request setDidReceiveDataBlock:NULL]; + + [_request cancel]; + } + + if (_sha256Ctx != NULL) { + free(_sha256Ctx); + } + + [self _closeAudioFileStream]; + + if ([DOUAudioStreamer options] & DOUAudioStreamerRemoveCacheOnDeallocation) { + [[NSFileManager defaultManager] removeItemAtPath:_cachedPath error:NULL]; + } } + (NSString *)_sha256ForAudioFileURL:(NSURL *)audioFileURL { - NSString *string = [audioFileURL absoluteString]; - unsigned char hash[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256([string UTF8String], (CC_LONG)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], hash); - - NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; - for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { - [result appendFormat:@"%02x", hash[i]]; - } - - return result; + NSString *string = [audioFileURL absoluteString]; + unsigned char hash[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256([string UTF8String], (CC_LONG)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], hash); + + NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { + [result appendFormat:@"%02x", hash[i]]; + } + + return result; } + (NSString *)_cachedPathForAudioFileURL:(NSURL *)audioFileURL { - NSString *filename = [NSString stringWithFormat:@"douas-%@.tmp", [self _sha256ForAudioFileURL:audioFileURL]]; - return [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; + NSString *filename = [NSString stringWithFormat:@"%@.dou", [self _sha256ForAudioFileURL:audioFileURL]]; + return [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; } - (void)_invokeEventBlock { - if (_eventBlock != NULL) { - _eventBlock(); - } + if (_eventBlock != NULL) { + _eventBlock(); + } } - (void)_requestDidComplete { - if ([_request isFailed] || - !([_request statusCode] >= 200 && [_request statusCode] < 300)) { - _failed = YES; - } - else { - _requestCompleted = YES; - [_mappedData dou_synchronizeMappedFile]; - } - - if (!_failed && - _sha256Ctx != NULL) { - unsigned char hash[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256_Final(hash, _sha256Ctx); - - NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; - for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { - [result appendFormat:@"%02x", hash[i]]; + if ([_request isFailed] || + !([_request statusCode] >= 200 && [_request statusCode] < 300)) { + _failed = YES; } - - _sha256 = [result copy]; - } - - if (gHintFile != nil && - gHintProvider == nil) { - gHintProvider = [[[self class] alloc] _initWithAudioFile:gHintFile]; - } - - [self _invokeEventBlock]; + else { + _requestCompleted = YES; + [_mappedData dou_synchronizeMappedFile]; + } + + if (!_failed && + _sha256Ctx != NULL) { + unsigned char hash[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256_Final(hash, _sha256Ctx); + + NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { + [result appendFormat:@"%02x", hash[i]]; + } + + _sha256 = [result copy]; + } + + if (gHintFile != nil && + gHintProvider == nil) { + gHintProvider = [[[self class] alloc] _initWithAudioFile:gHintFile]; + } + + [self _invokeEventBlock]; } - (void)_requestDidReportProgress:(double)progress { - [self _invokeEventBlock]; + [self _invokeEventBlock]; } - (void)_requestDidReceiveResponse { - _expectedLength = [_request responseContentLength]; - - _cachedPath = [[self class] _cachedPathForAudioFileURL:_audioFileURL]; - _cachedURL = [NSURL fileURLWithPath:_cachedPath]; - - [[NSFileManager defaultManager] createFileAtPath:_cachedPath contents:nil attributes:nil]; + _expectedLength = [_request responseContentLength]; + + _cachedPath = [[self class] _cachedPathForAudioFileURL:_audioFileURL]; + _cachedURL = [NSURL fileURLWithPath:_cachedPath]; + + [[NSFileManager defaultManager] createFileAtPath:_cachedPath contents:nil attributes:nil]; #if TARGET_OS_IPHONE - [[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey: NSFileProtectionNone} - ofItemAtPath:_cachedPath - error:NULL]; + [[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey: NSFileProtectionNone} + ofItemAtPath:_cachedPath + error:NULL]; #endif /* TARGET_OS_IPHONE */ - [[NSFileHandle fileHandleForWritingAtPath:_cachedPath] truncateFileAtOffset:_expectedLength]; - - _mimeType = [[_request responseHeaders] objectForKey:@"Content-Type"]; - - _mappedData = [NSData dou_modifiableDataWithMappedContentsOfFile:_cachedPath]; + [[NSFileHandle fileHandleForWritingAtPath:_cachedPath] truncateFileAtOffset:_expectedLength]; + + _mimeType = [[_request responseHeaders] objectForKey:@"Content-Type"]; + + _mappedData = [NSData dou_modifiableDataWithMappedContentsOfFile:_cachedPath]; + + + } - (void)_requestDidReceiveData:(NSData *)data { - if (_mappedData == nil) { - return; - } - - NSUInteger availableSpace = _expectedLength - _receivedLength; - NSUInteger bytesToWrite = MIN(availableSpace, [data length]); - - memcpy((uint8_t *)[_mappedData bytes] + _receivedLength, [data bytes], bytesToWrite); - _receivedLength += bytesToWrite; - - if (_sha256Ctx != NULL) { - CC_SHA256_Update(_sha256Ctx, [data bytes], (CC_LONG)[data length]); - } - - if (!_readyToProducePackets && !_failed && !_requiresCompleteFile) { - OSStatus status = kAudioFileStreamError_UnsupportedFileType; - - if (_audioFileStreamID != NULL) { - status = AudioFileStreamParseBytes(_audioFileStreamID, - (UInt32)[data length], - [data bytes], - 0); + if (_mappedData == nil) { + return; } - - if (status != noErr && status != kAudioFileStreamError_NotOptimized) { - NSArray *fallbackTypeIDs = [self _fallbackTypeIDs]; - for (NSNumber *typeIDNumber in fallbackTypeIDs) { - AudioFileTypeID typeID = (AudioFileTypeID)[typeIDNumber unsignedLongValue]; - [self _closeAudioFileStream]; - [self _openAudioFileStreamWithFileTypeHint:typeID]; - + + NSUInteger availableSpace = _expectedLength - _receivedLength; + NSUInteger bytesToWrite = MIN(availableSpace, [data length]); + + memcpy((uint8_t *)[_mappedData bytes] + _receivedLength, [data bytes], bytesToWrite); + _receivedLength += bytesToWrite; + + if (_sha256Ctx != NULL) { + CC_SHA256_Update(_sha256Ctx, [data bytes], (CC_LONG)[data length]); + } + + if (!_readyToProducePackets && !_failed && !_requiresCompleteFile) { + OSStatus status = kAudioFileStreamError_UnsupportedFileType; + if (_audioFileStreamID != NULL) { - status = AudioFileStreamParseBytes(_audioFileStreamID, - (UInt32)_receivedLength, - [_mappedData bytes], - 0); - - if (status == noErr || status == kAudioFileStreamError_NotOptimized) { - break; - } + status = AudioFileStreamParseBytes(_audioFileStreamID, + (UInt32)[data length], + [data bytes], + 0); + } + + if (status != noErr && status != kAudioFileStreamError_NotOptimized) { + NSArray *fallbackTypeIDs = [self _fallbackTypeIDs]; + for (NSNumber *typeIDNumber in fallbackTypeIDs) { + AudioFileTypeID typeID = (AudioFileTypeID)[typeIDNumber unsignedLongValue]; + [self _closeAudioFileStream]; + [self _openAudioFileStreamWithFileTypeHint:typeID]; + + if (_audioFileStreamID != NULL) { + status = AudioFileStreamParseBytes(_audioFileStreamID, + (UInt32)_receivedLength, + [_mappedData bytes], + 0); + + if (status == noErr || status == kAudioFileStreamError_NotOptimized) { + break; + } + } + } + + if (status != noErr && status != kAudioFileStreamError_NotOptimized) { + _failed = YES; + } + } + + if (status == kAudioFileStreamError_NotOptimized) { + [self _closeAudioFileStream]; + _requiresCompleteFile = YES; } - } - - if (status != noErr && status != kAudioFileStreamError_NotOptimized) { - _failed = YES; - } - } - - if (status == kAudioFileStreamError_NotOptimized) { - [self _closeAudioFileStream]; - _requiresCompleteFile = YES; } - } } - (void)_createRequest @@ -380,13 +388,14 @@ - (void)_createRequest [_request setDidReceiveDataBlock:^(NSData *data) { [_self _requestDidReceiveData:data]; }]; + } - (void)_handleAudioFileStreamProperty:(AudioFileStreamPropertyID)propertyID { - if (propertyID == kAudioFileStreamProperty_ReadyToProducePackets) { - _readyToProducePackets = YES; - } + if (propertyID == kAudioFileStreamProperty_ReadyToProducePackets) { + _readyToProducePackets = YES; + } } - (void)_handleAudioFileStreamPackets:(const void *)packets @@ -397,12 +406,12 @@ - (void)_handleAudioFileStreamPackets:(const void *)packets } static void audio_file_stream_property_listener_proc(void *inClientData, - AudioFileStreamID inAudioFileStream, - AudioFileStreamPropertyID inPropertyID, - UInt32 *ioFlags) + AudioFileStreamID inAudioFileStream, + AudioFileStreamPropertyID inPropertyID, + UInt32 *ioFlags) { - __unsafe_unretained _DOUAudioRemoteFileProvider *fileProvider = (__bridge _DOUAudioRemoteFileProvider *)inClientData; - [fileProvider _handleAudioFileStreamProperty:inPropertyID]; + __unsafe_unretained _DOUAudioRemoteFileProvider *fileProvider = (__bridge _DOUAudioRemoteFileProvider *)inClientData; + [fileProvider _handleAudioFileStreamProperty:inPropertyID]; } static void audio_file_stream_packets_proc(void *inClientData, @@ -411,123 +420,123 @@ static void audio_file_stream_packets_proc(void *inClientData, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions) { - __unsafe_unretained _DOUAudioRemoteFileProvider *fileProvider = (__bridge _DOUAudioRemoteFileProvider *)inClientData; - [fileProvider _handleAudioFileStreamPackets:inInputData - numberOfBytes:inNumberBytes - numberOfPackets:inNumberPackets - packetDescriptions:inPacketDescriptions]; + __unsafe_unretained _DOUAudioRemoteFileProvider *fileProvider = (__bridge _DOUAudioRemoteFileProvider *)inClientData; + [fileProvider _handleAudioFileStreamPackets:inInputData + numberOfBytes:inNumberBytes + numberOfPackets:inNumberPackets + packetDescriptions:inPacketDescriptions]; } - (void)_openAudioFileStream { - [self _openAudioFileStreamWithFileTypeHint:0]; + [self _openAudioFileStreamWithFileTypeHint:0]; } - (void)_openAudioFileStreamWithFileTypeHint:(AudioFileTypeID)fileTypeHint { - OSStatus status = AudioFileStreamOpen((__bridge void *)self, - audio_file_stream_property_listener_proc, - audio_file_stream_packets_proc, - fileTypeHint, - &_audioFileStreamID); - - if (status != noErr) { - _audioFileStreamID = NULL; - } + OSStatus status = AudioFileStreamOpen((__bridge void *)self, + audio_file_stream_property_listener_proc, + audio_file_stream_packets_proc, + fileTypeHint, + &_audioFileStreamID); + + if (status != noErr) { + _audioFileStreamID = NULL; + } } - (void)_closeAudioFileStream { - if (_audioFileStreamID != NULL) { - AudioFileStreamClose(_audioFileStreamID); - _audioFileStreamID = NULL; - } + if (_audioFileStreamID != NULL) { + AudioFileStreamClose(_audioFileStreamID); + _audioFileStreamID = NULL; + } } - (NSArray *)_fallbackTypeIDs { - NSMutableArray *fallbackTypeIDs = [NSMutableArray array]; - NSMutableSet *fallbackTypeIDSet = [NSMutableSet set]; - - struct { - CFStringRef specifier; - AudioFilePropertyID propertyID; - } properties[] = { - { (__bridge CFStringRef)[self mimeType], kAudioFileGlobalInfo_TypesForMIMEType }, - { (__bridge CFStringRef)[self fileExtension], kAudioFileGlobalInfo_TypesForExtension } - }; - - const size_t numberOfProperties = sizeof(properties) / sizeof(properties[0]); - - for (size_t i = 0; i < numberOfProperties; ++i) { - if (properties[i].specifier == NULL) { - continue; - } - - UInt32 outSize = 0; - OSStatus status; - - status = AudioFileGetGlobalInfoSize(properties[i].propertyID, + NSMutableArray *fallbackTypeIDs = [NSMutableArray array]; + NSMutableSet *fallbackTypeIDSet = [NSMutableSet set]; + + struct { + CFStringRef specifier; + AudioFilePropertyID propertyID; + } properties[] = { + { (__bridge CFStringRef)[self mimeType], kAudioFileGlobalInfo_TypesForMIMEType }, + { (__bridge CFStringRef)[self fileExtension], kAudioFileGlobalInfo_TypesForExtension } + }; + + const size_t numberOfProperties = sizeof(properties) / sizeof(properties[0]); + + for (size_t i = 0; i < numberOfProperties; ++i) { + if (properties[i].specifier == NULL) { + continue; + } + + UInt32 outSize = 0; + OSStatus status; + + status = AudioFileGetGlobalInfoSize(properties[i].propertyID, + sizeof(properties[i].specifier), + &properties[i].specifier, + &outSize); + if (status != noErr) { + continue; + } + + size_t count = outSize / sizeof(AudioFileTypeID); + AudioFileTypeID *buffer = (AudioFileTypeID *)malloc(outSize); + if (buffer == NULL) { + continue; + } + + status = AudioFileGetGlobalInfo(properties[i].propertyID, sizeof(properties[i].specifier), &properties[i].specifier, - &outSize); - if (status != noErr) { - continue; - } - - size_t count = outSize / sizeof(AudioFileTypeID); - AudioFileTypeID *buffer = (AudioFileTypeID *)malloc(outSize); - if (buffer == NULL) { - continue; - } - - status = AudioFileGetGlobalInfo(properties[i].propertyID, - sizeof(properties[i].specifier), - &properties[i].specifier, - &outSize, - buffer); - if (status != noErr) { - free(buffer); - continue; - } - - for (size_t j = 0; j < count; ++j) { - NSNumber *tid = [NSNumber numberWithUnsignedLong:buffer[j]]; - if ([fallbackTypeIDSet containsObject:tid]) { - continue; - } - - [fallbackTypeIDs addObject:tid]; - [fallbackTypeIDSet addObject:tid]; + &outSize, + buffer); + if (status != noErr) { + free(buffer); + continue; + } + + for (size_t j = 0; j < count; ++j) { + NSNumber *tid = [NSNumber numberWithUnsignedLong:buffer[j]]; + if ([fallbackTypeIDSet containsObject:tid]) { + continue; + } + + [fallbackTypeIDs addObject:tid]; + [fallbackTypeIDSet addObject:tid]; + } + + free(buffer); } - - free(buffer); - } - - return fallbackTypeIDs; + + return fallbackTypeIDs; } - (NSString *)fileExtension { - if (_fileExtension == nil) { - _fileExtension = [[[[self audioFile] audioFileURL] path] pathExtension]; - } - - return _fileExtension; + if (_fileExtension == nil) { + _fileExtension = [[[[self audioFile] audioFileURL] path] pathExtension]; + } + + return _fileExtension; } - (NSUInteger)downloadSpeed { - return [_request downloadSpeed]; + return [_request downloadSpeed]; } - (BOOL)isReady { - if (!_requiresCompleteFile) { - return _readyToProducePackets; - } - - return _requestCompleted; + if (!_requiresCompleteFile) { + return _readyToProducePackets; + } + + return _requestCompleted; } @end @@ -539,98 +548,98 @@ @implementation _DOUAudioMediaLibraryFileProvider - (instancetype)_initWithAudioFile:(id )audioFile { - self = [super _initWithAudioFile:audioFile]; - if (self) { - [self _createAssetLoader]; - [_assetLoader start]; - } - - return self; + self = [super _initWithAudioFile:audioFile]; + if (self) { + [self _createAssetLoader]; + [_assetLoader start]; + } + + return self; } - (void)dealloc { - @synchronized(_assetLoader) { - [_assetLoader setCompletedBlock:NULL]; - [_assetLoader cancel]; - } - - [[NSFileManager defaultManager] removeItemAtPath:[_assetLoader cachedPath] - error:NULL]; + @synchronized(_assetLoader) { + [_assetLoader setCompletedBlock:NULL]; + [_assetLoader cancel]; + } + + [[NSFileManager defaultManager] removeItemAtPath:[_assetLoader cachedPath] + error:NULL]; } - (void)_invokeEventBlock { - if (_eventBlock != NULL) { - _eventBlock(); - } + if (_eventBlock != NULL) { + _eventBlock(); + } } - (void)_assetLoaderDidComplete { - if ([_assetLoader isFailed]) { - _failed = YES; + if ([_assetLoader isFailed]) { + _failed = YES; + [self _invokeEventBlock]; + return; + } + + _mimeType = [_assetLoader mimeType]; + _fileExtension = [_assetLoader fileExtension]; + + _cachedPath = [_assetLoader cachedPath]; + _cachedURL = [NSURL fileURLWithPath:_cachedPath]; + + _mappedData = [NSData dou_dataWithMappedContentsOfFile:_cachedPath]; + _expectedLength = [_mappedData length]; + _receivedLength = [_mappedData length]; + + _loaderCompleted = YES; [self _invokeEventBlock]; - return; - } - - _mimeType = [_assetLoader mimeType]; - _fileExtension = [_assetLoader fileExtension]; - - _cachedPath = [_assetLoader cachedPath]; - _cachedURL = [NSURL fileURLWithPath:_cachedPath]; - - _mappedData = [NSData dou_dataWithMappedContentsOfFile:_cachedPath]; - _expectedLength = [_mappedData length]; - _receivedLength = [_mappedData length]; - - _loaderCompleted = YES; - [self _invokeEventBlock]; } - (void)_createAssetLoader { - _assetLoader = [DOUMPMediaLibraryAssetLoader loaderWithURL:[_audioFile audioFileURL]]; - - __weak typeof(self) weakSelf = self; - [_assetLoader setCompletedBlock:^{ - __strong typeof(weakSelf) strongSelf = weakSelf; - [strongSelf _assetLoaderDidComplete]; - }]; + _assetLoader = [DOUMPMediaLibraryAssetLoader loaderWithURL:[_audioFile audioFileURL]]; + + __weak typeof(self) weakSelf = self; + [_assetLoader setCompletedBlock:^{ + __strong typeof(weakSelf) strongSelf = weakSelf; + [strongSelf _assetLoaderDidComplete]; + }]; } - (NSString *)sha256 { - if (_sha256 == nil && - [DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256 && - [self mappedData] != nil) { - unsigned char hash[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256([[self mappedData] bytes], (CC_LONG)[[self mappedData] length], hash); - - NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; - for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { - [result appendFormat:@"%02x", hash[i]]; + if (_sha256 == nil && + [DOUAudioStreamer options] & DOUAudioStreamerRequireSHA256 && + [self mappedData] != nil) { + unsigned char hash[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256([[self mappedData] bytes], (CC_LONG)[[self mappedData] length], hash); + + NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { + [result appendFormat:@"%02x", hash[i]]; + } + + _sha256 = [result copy]; } - - _sha256 = [result copy]; - } - - return _sha256; + + return _sha256; } - (NSUInteger)downloadSpeed { - return _receivedLength; + return _receivedLength; } - (BOOL)isReady { - return _loaderCompleted; + return _loaderCompleted; } - (BOOL)isFinished { - return _loaderCompleted; + return _loaderCompleted; } @end @@ -654,104 +663,148 @@ @implementation DOUAudioFileProvider + (instancetype)_fileProviderWithAudioFile:(id )audioFile { - if (audioFile == nil) { - return nil; - } - - NSURL *audioFileURL = [audioFile audioFileURL]; - if (audioFileURL == nil) { - return nil; - } - - if ([audioFileURL isFileURL]) { - return [[_DOUAudioLocalFileProvider alloc] _initWithAudioFile:audioFile]; - } + if (audioFile == nil) { + return nil; + } + + __block NSURL *audioFileURL = [audioFile audioFileURL]; + if (audioFileURL == nil) { + return nil; + } + + /// load data from cache first + NSString *localPath = [_DOUAudioRemoteFileProvider _cachedPathForAudioFileURL:audioFileURL]; + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir = NO; + __block DOUTrack *localTrack = nil; + void(^getLocalTrack)(NSString* aPath) = ^(NSString *aPath) { + audioFileURL = [[NSURL alloc]initFileURLWithPath:aPath]; + localTrack = [[DOUTrack alloc]init]; + localTrack.audioFileURL = audioFileURL; + }; + if ([fm fileExistsAtPath:localPath isDirectory:&isDir]) { + getLocalTrack(localPath); + } else { /// search for additional cache path + NSArray* paths = [DOUCacheManager shared].addtionalCachePaths; + NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true).firstObject; + if (paths && paths.count && cachePath) { + for (NSString* diretory in paths) { + NSString *dir = [cachePath stringByAppendingPathComponent:diretory]; + NSArray *ar = [fm contentsOfDirectoryAtPath:dir error:nil]; + if (ar.count == 1) { + if ([ar.firstObject isEqualToString:@"._DStore"]) { + continue; + } + } + if (ar.count == 0) { + continue; + } + NSString *filename = [NSString stringWithFormat:@"%@.dou", [_DOUAudioRemoteFileProvider _sha256ForAudioFileURL:audioFileURL]]; + NSString *filePath = [dir stringByAppendingPathComponent:filename]; + if ([fm fileExistsAtPath:filePath isDirectory:&isDir]) { + getLocalTrack(filePath); + break; + } + + } + } + + + } + + if ([audioFileURL isFileURL]) { + id finalTrack = audioFile; + if (localTrack != nil) { + finalTrack = localTrack; + } + return [[_DOUAudioLocalFileProvider alloc] _initWithAudioFile: finalTrack]; + } #if TARGET_OS_IPHONE - else if ([[audioFileURL scheme] isEqualToString:@"ipod-library"]) { - return [[_DOUAudioMediaLibraryFileProvider alloc] _initWithAudioFile:audioFile]; - } + else if ([[audioFileURL scheme] isEqualToString:@"ipod-library"]) { + return [[_DOUAudioMediaLibraryFileProvider alloc] _initWithAudioFile:audioFile]; + } #endif /* TARGET_OS_IPHONE */ - else { - return [[_DOUAudioRemoteFileProvider alloc] _initWithAudioFile:audioFile]; - } + else { + return [[_DOUAudioRemoteFileProvider alloc] _initWithAudioFile:audioFile]; + } } + (instancetype)fileProviderWithAudioFile:(id )audioFile { - if ((audioFile == gHintFile || - [audioFile isEqual:gHintFile]) && - gHintProvider != nil) { - DOUAudioFileProvider *provider = gHintProvider; + if ((audioFile == gHintFile || + [audioFile isEqual:gHintFile]) && + gHintProvider != nil) { + DOUAudioFileProvider *provider = gHintProvider; + gHintFile = nil; + gHintProvider = nil; + gLastProviderIsFinished = [provider isFinished]; + + return provider; + } + gHintFile = nil; gHintProvider = nil; - gLastProviderIsFinished = [provider isFinished]; - - return provider; - } - - gHintFile = nil; - gHintProvider = nil; - gLastProviderIsFinished = NO; - - return [self _fileProviderWithAudioFile:audioFile]; + gLastProviderIsFinished = NO; + + return [self _fileProviderWithAudioFile:audioFile]; } + (void)setHintWithAudioFile:(id )audioFile { - if (audioFile == gHintFile || - [audioFile isEqual:gHintFile]) { - return; - } - - gHintFile = nil; - gHintProvider = nil; - - if (audioFile == nil) { - return; - } - - NSURL *audioFileURL = [audioFile audioFileURL]; - if (audioFileURL == nil || + if (audioFile == gHintFile || + [audioFile isEqual:gHintFile]) { + return; + } + + gHintFile = nil; + gHintProvider = nil; + + if (audioFile == nil) { + return; + } + + NSURL *audioFileURL = [audioFile audioFileURL]; + if (audioFileURL == nil || #if TARGET_OS_IPHONE - [[audioFileURL scheme] isEqualToString:@"ipod-library"] || + [[audioFileURL scheme] isEqualToString:@"ipod-library"] || #endif /* TARGET_OS_IPHONE */ - [audioFileURL isFileURL]) { - return; - } - - gHintFile = audioFile; - - if (gLastProviderIsFinished) { - gHintProvider = [self _fileProviderWithAudioFile:gHintFile]; - } + [audioFileURL isFileURL]) { + return; + } + + gHintFile = audioFile; + + if (gLastProviderIsFinished) { + gHintProvider = [self _fileProviderWithAudioFile:gHintFile]; + } } - (instancetype)_initWithAudioFile:(id )audioFile { - self = [super init]; - if (self) { - _audioFile = audioFile; - } - - return self; + self = [super init]; + if (self) { + _audioFile = audioFile; + } + + return self; } - (NSUInteger)downloadSpeed { - [self doesNotRecognizeSelector:_cmd]; - return 0; + [self doesNotRecognizeSelector:_cmd]; + return 0; } - (BOOL)isReady { - [self doesNotRecognizeSelector:_cmd]; - return NO; + [self doesNotRecognizeSelector:_cmd]; + return NO; } - (BOOL)isFinished { - [self doesNotRecognizeSelector:_cmd]; - return NO; + [self doesNotRecognizeSelector:_cmd]; + return NO; } @end diff --git a/src/DOUCacheManager.h b/src/DOUCacheManager.h new file mode 100644 index 0000000..d1e0c7e --- /dev/null +++ b/src/DOUCacheManager.h @@ -0,0 +1,32 @@ +// +// SSCacheManager.h +// Pods +// +// Created by LawLincoln on 15/9/23. +// +// + +#import +#import "DOUAudioStreamer+Options.h" +@class VerifyInfo; +typedef BOOL(^VerifyClosure)(NSData* _Nullable, VerifyInfo* _Nullable); +@interface DOUCacheManager : NSObject +@property (nonatomic, copy) VerifyClosure _Nullable verifyClosure; +@property (nonatomic, strong) NSDictionary* _Nullable customHeader; ++(nonnull DOUCacheManager*)shared; +- (void) manualManagerRemoteAudioFileCache:(DOUAudioStreamerOptions)opt maximumFileCount:(NSUInteger)count; +- (void) cleanUselessCache; +- (void) cleanAllCache; +- (void) cleanCacheWithURL:(nonnull NSURL*)url; +- (void) addSearchCachePath:(nullable NSString*)path; +- (void)removeCachePath:(nullable NSString*)path; +- (nullable NSArray*) addtionalCachePaths; +- (void) moveFileToAddtionalCachePath:(nonnull NSURL*)url; +- (void) checkFileCompeletionForURL:(nonnull NSURL*)url; +@end + +@interface VerifyInfo: NSObject +@property (nonatomic, copy) NSString * _Nullable Etag; +@property (nonatomic, copy) NSString * _Nullable ContentLength; +- (void)encodeWithCoder:(NSCoder* _Nullable)coder; +@end diff --git a/src/DOUCacheManager.m b/src/DOUCacheManager.m new file mode 100644 index 0000000..1a776ce --- /dev/null +++ b/src/DOUCacheManager.m @@ -0,0 +1,286 @@ +// +// SSCacheManager.m +// Pods +// +// Created by LawLincoln on 15/9/23. +// +// + +#import "DOUCacheManager.h" +#include +@implementation VerifyInfo +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeObject: _Etag forKey: @"Etag"]; + [coder encodeObject: _ContentLength forKey: @"ContentLenght"]; + +} +- (instancetype)initWithCoder:(NSCoder *)coder +{ + if (self = [super init]) { + self.Etag = [coder decodeObjectForKey:@"Etag"]; + self.ContentLength = [coder decodeObjectForKey:@"ContentLenght"]; + } + return self; +} + +@end +@interface DOUCacheManager() +@property (nonatomic, assign) NSUInteger maximumCacheFile; +@property (nonatomic, copy) NSMutableArray* cachePaths; +@property (nonatomic, strong) NSMutableDictionary* storeInfo; +@property (nonatomic) dispatch_queue_t wokerQueue; +@end +@implementation DOUCacheManager ++ (nonnull DOUCacheManager *) shared { + static DOUCacheManager *sharedMyManager = nil; + @synchronized(self) { + if (sharedMyManager == nil) + sharedMyManager = [[DOUCacheManager alloc] init]; + } + return sharedMyManager; +} + +- (void) manualManagerRemoteAudioFileCache:(DOUAudioStreamerOptions)opt maximumFileCount:(NSUInteger)count { + [DOUAudioStreamer setOptions:opt]; + _maximumCacheFile = count; + [self cleanUselessCache]; +} +/* + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:@"path/to/my/file" error:nil]; + + NSDate *date = [attributes fileModificationDate]; + + */ + +- (NSMutableArray* __nullable)cacheFiles { + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *path = NSTemporaryDirectory(); + + NSArray *array = [fm contentsOfDirectoryAtPath:path error:nil]; + if (array == nil) { + return nil; + } + + NSMutableArray *douArray = [NSMutableArray array]; + for (NSString *file in array) { + if ([file hasSuffix: @".dou"]) { + [douArray addObject:file]; + } + } + return douArray; +} + ++ (NSString *)_sha256ForAudioFileURL:(nonnull NSURL *)audioFileURL +{ + NSString *string = [audioFileURL absoluteString]; + unsigned char hash[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256([string UTF8String], (CC_LONG)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], hash); + + NSMutableString *result = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (size_t i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) { + [result appendFormat:@"%02x", hash[i]]; + } + + return result; +} + ++ (NSString *)_cachedPathForAudioFileURL:(NSURL *)audioFileURL +{ + NSString *filename = [NSString stringWithFormat:@"%@.dou", [self _sha256ForAudioFileURL:audioFileURL]]; + return [NSTemporaryDirectory() stringByAppendingPathComponent:filename]; +} + +- (void)cleanCacheWithURL:(NSURL*)url { + if (url == nil) { + return; + } + NSString *localPath = [[self class] _cachedPathForAudioFileURL:url]; + [[NSFileManager defaultManager] removeItemAtPath:localPath error:nil]; +} + +- (void) cleanUselessCache { + NSMutableArray *douArray = [self cacheFiles]; + if (douArray == nil || douArray.count <= _maximumCacheFile) { + return; + } + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *path = NSTemporaryDirectory(); + NSUInteger needToRemove = douArray.count - _maximumCacheFile; + + NSDate*(^lastModificationDate)(NSString *file) = ^NSDate*(NSString* file) { + NSString *filePath = [path stringByAppendingPathComponent:file]; + NSDictionary *attributes = [fm attributesOfItemAtPath:filePath error:nil]; + NSDate *date = [attributes fileModificationDate]; + return date; + }; + + [douArray sortUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) { + NSDate *d1 = lastModificationDate(obj1); + NSDate *d2 = lastModificationDate(obj2); + return [d2 compare:d1]; + }]; + + for (NSUInteger i = 0; i < needToRemove; i++) { + NSString *filePath = [path stringByAppendingPathComponent:douArray.lastObject]; + [fm removeItemAtPath:filePath error:nil]; + [douArray removeLastObject]; + } +} + +- (void) cleanAllCache { + NSMutableArray *douArray = [self cacheFiles]; + if (douArray == nil || douArray.count == 0) { + return; + } + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *path = NSTemporaryDirectory(); + for (NSString *file in douArray) { + NSString *filePath = [path stringByAppendingPathComponent:file]; + [fm removeItemAtPath:filePath error:nil]; + } +} +- (NSMutableArray *)cachePaths { + if (!_cachePaths) { + _cachePaths = [NSMutableArray array]; + NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true).firstObject; + path = [path stringByAppendingPathComponent: @"searchPaths"]; + id array = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; + if ([array isKindOfClass:[NSArray class]]) { + _cachePaths = array; + } + } + return _cachePaths; +} + +- (void)addSearchCachePath:(nullable NSString *)path { + if (path) { + if ([self.cachePaths indexOfObject:path] == NSNotFound) { + [self.cachePaths addObject:path]; + dispatch_async(self.wokerQueue, ^{ + NSString *path = [self pathForSearchPaths]; + [NSKeyedArchiver archiveRootObject:self.cachePaths toFile:path]; + }); + } + } +} + +- (void)removeCachePath:(nullable NSString*)path { + if (path) { + if ([self.cachePaths indexOfObject:path] != NSNotFound) { + [self.cachePaths removeObject:path]; + dispatch_async(self.wokerQueue, ^{ + NSString *path = [self pathForSearchPaths]; + [NSKeyedArchiver archiveRootObject:self.cachePaths toFile:path]; + }); + } + } +} + +- (NSString*) pathForSearchPaths { + NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true).firstObject; + path = [path stringByAppendingPathComponent: @"searchPaths"]; + return path; +} + +- (NSArray*)addtionalCachePaths { + return self.cachePaths; +} + + +- (void) moveFileToAddtionalCachePath:(NSURL *)audioFileURL { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSString *destFilePath = [self pathForMoveFileToAddtionalCachePath: audioFileURL]; + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir = NO; + BOOL fileExist = [fm fileExistsAtPath:destFilePath isDirectory:&isDir]; + if (fileExist) { + return; + } + NSString *fileName = [NSString stringWithFormat:@"%@.dou", [[self class] _sha256ForAudioFileURL:audioFileURL]]; + NSString *tmpPath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName]; + BOOL tmpExist = [fm fileExistsAtPath:tmpPath isDirectory:&isDir]; + if (destFilePath != nil && !fileExist && tmpExist) { + [fm moveItemAtPath:tmpPath toPath:destFilePath error:nil]; + } + }); +} + +- (NSString*) pathForMoveFileToAddtionalCachePath:(nonnull NSURL *)audioFileURL { + NSArray *diretory = [DOUCacheManager shared].addtionalCachePaths; + if ( diretory != nil ) { + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir = NO; + NSString *filename = [NSString stringWithFormat:@"%@.dou", [[self class] _sha256ForAudioFileURL:audioFileURL]]; + for (NSString* dir in diretory) { + NSString *filePath = [dir stringByAppendingPathComponent:filename]; + if (![fm fileExistsAtPath:filePath isDirectory:&isDir]) { + return filePath; + } + } + } + return nil; +} + + +#pragma mark - +- (dispatch_queue_t)wokerQueue { + if (!_wokerQueue) { + _wokerQueue = dispatch_queue_create("DoucacheManager.wokerQueue", nil); + } + return _wokerQueue; +} + +#pragma mark - + +- (NSMutableDictionary *)storeInfo { + if (!_storeInfo) { + _storeInfo = [NSMutableDictionary dictionary]; + id obj = [NSKeyedUnarchiver unarchiveObjectWithFile: [self verifyInfoStorePath]]; + if (obj && [obj isKindOfClass:[NSDictionary class]]) { + [_storeInfo addEntriesFromDictionary:obj]; + } + } + return _storeInfo; +} +- (NSString*) verifyInfoStorePath { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *path = @""; + if (paths && paths.count > 0) { + path = paths[0]; + } + path = [path stringByAppendingPathComponent:@"douVerifyInfo"]; + return path; +} + +- (void) storeInfo:(VerifyInfo*)info forURL:(NSString*)url { + __weak DOUCacheManager *wself = self; + dispatch_async(self.wokerQueue, ^{ + if (wself.storeInfo) { + if (info && url) { + wself.storeInfo[url] = info; + } + [NSKeyedArchiver archiveRootObject: wself.storeInfo toFile: [wself verifyInfoStorePath]]; + } + }); +} +- (void)checkFileCompeletionForURL:(NSURL * _Nonnull)url { + if (_verifyClosure == nil) { + return; + } + __weak DOUCacheManager *wself = self; + dispatch_async(self.wokerQueue, ^{ + NSString *filePath = [[wself class] _cachedPathForAudioFileURL:url]; + if (wself.storeInfo) { + VerifyInfo *info = wself.storeInfo[url.absoluteString]; + if (filePath && info) { + NSData *data = [[NSData alloc]initWithContentsOfFile:filePath]; + BOOL isComplete = _verifyClosure(data, info); + if (!isComplete) { + [wself cleanCacheWithURL:url]; + } + } + } + }); +} +@end diff --git a/src/DOUSimpleHTTPRequest.m b/src/DOUSimpleHTTPRequest.m index df874c4..19d3c91 100644 --- a/src/DOUSimpleHTTPRequest.m +++ b/src/DOUSimpleHTTPRequest.m @@ -18,86 +18,90 @@ #include #include #include - +#import "DOUCacheManager.h" +@interface DOUCacheManager (private) +- (void) storeInfo:(VerifyInfo*)info forURL:(NSString*)url; +@end static struct { - pthread_t thread; - pthread_mutex_t mutex; - pthread_cond_t cond; - CFRunLoopRef runloop; + pthread_t thread; + pthread_mutex_t mutex; + pthread_cond_t cond; + CFRunLoopRef runloop; } controller; static void *controller_main(void *info) { - pthread_setname_np("com.douban.simple-http-request.controller"); - - pthread_mutex_lock(&controller.mutex); - controller.runloop = CFRunLoopGetCurrent(); - pthread_mutex_unlock(&controller.mutex); - pthread_cond_signal(&controller.cond); - - CFRunLoopSourceContext context; - bzero(&context, sizeof(context)); - - CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); - CFRunLoopAddSource(controller.runloop, source, kCFRunLoopDefaultMode); - - CFRunLoopRun(); - - CFRunLoopRemoveSource(controller.runloop, source, kCFRunLoopDefaultMode); - CFRelease(source); - - pthread_mutex_destroy(&controller.mutex); - pthread_cond_destroy(&controller.cond); - - return NULL; + pthread_setname_np("com.douban.simple-http-request.controller"); + + pthread_mutex_lock(&controller.mutex); + controller.runloop = CFRunLoopGetCurrent(); + pthread_mutex_unlock(&controller.mutex); + pthread_cond_signal(&controller.cond); + + CFRunLoopSourceContext context; + bzero(&context, sizeof(context)); + + CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); + CFRunLoopAddSource(controller.runloop, source, kCFRunLoopDefaultMode); + + CFRunLoopRun(); + + CFRunLoopRemoveSource(controller.runloop, source, kCFRunLoopDefaultMode); + CFRelease(source); + + pthread_mutex_destroy(&controller.mutex); + pthread_cond_destroy(&controller.cond); + + return NULL; } static CFRunLoopRef controller_get_runloop() { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_mutex_init(&controller.mutex, NULL); - pthread_cond_init(&controller.cond, NULL); - controller.runloop = NULL; - - pthread_create(&controller.thread, NULL, controller_main, NULL); - - pthread_mutex_lock(&controller.mutex); - if (controller.runloop == NULL) { - pthread_cond_wait(&controller.cond, &controller.mutex); - } - pthread_mutex_unlock(&controller.mutex); - }); - - return controller.runloop; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + pthread_mutex_init(&controller.mutex, NULL); + pthread_cond_init(&controller.cond, NULL); + controller.runloop = NULL; + + pthread_create(&controller.thread, NULL, controller_main, NULL); + + pthread_mutex_lock(&controller.mutex); + if (controller.runloop == NULL) { + pthread_cond_wait(&controller.cond, &controller.mutex); + } + pthread_mutex_unlock(&controller.mutex); + }); + + return controller.runloop; } @interface DOUSimpleHTTPRequest () { @private - DOUSimpleHTTPRequestCompletedBlock _completedBlock; - DOUSimpleHTTPRequestProgressBlock _progressBlock; - DOUSimpleHTTPRequestDidReceiveResponseBlock _didReceiveResponseBlock; - DOUSimpleHTTPRequestDidReceiveDataBlock _didReceiveDataBlock; - - NSString *_userAgent; - NSTimeInterval _timeoutInterval; - - CFHTTPMessageRef _message; - CFReadStreamRef _responseStream; - - NSDictionary *_responseHeaders; - NSMutableData *_responseData; - NSString *_responseString; - - NSInteger _statusCode; - NSString *_statusMessage; - BOOL _failed; - - CFAbsoluteTime _startedTime; - NSUInteger _downloadSpeed; - - NSUInteger _responseContentLength; - NSUInteger _receivedLength; + DOUSimpleHTTPRequestCompletedBlock _completedBlock; + DOUSimpleHTTPRequestProgressBlock _progressBlock; + DOUSimpleHTTPRequestDidReceiveResponseBlock _didReceiveResponseBlock; + DOUSimpleHTTPRequestDidReceiveDataBlock _didReceiveDataBlock; + + NSString *_userAgent; + NSTimeInterval _timeoutInterval; + + CFHTTPMessageRef _message; + CFReadStreamRef _responseStream; + + NSDictionary *_responseHeaders; + NSMutableData *_responseData; + NSString *_responseString; + + NSInteger _statusCode; + NSString *_statusMessage; + BOOL _failed; + + CFAbsoluteTime _startedTime; + NSUInteger _downloadSpeed; + + NSUInteger _responseContentLength; + NSUInteger _receivedLength; + NSString *remoteURL; } @end @@ -123,342 +127,365 @@ @implementation DOUSimpleHTTPRequest + (instancetype)requestWithURL:(NSURL *)url { - if (url == nil) { - return nil; - } - - return [[[self class] alloc] initWithURL:url]; + if (url == nil) { + return nil; + } + + return [[[self class] alloc] initWithURL:url]; } - (instancetype)initWithURL:(NSURL *)url { - self = [super init]; - if (self) { - _userAgent = [[self class] defaultUserAgent]; - _timeoutInterval = [[self class] defaultTimeoutInterval]; - - _message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1); - } - - return self; + self = [super init]; + if (self) { + _userAgent = [[self class] defaultUserAgent]; + _timeoutInterval = [[self class] defaultTimeoutInterval]; + remoteURL = url.absoluteString; + _message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1); + } + + return self; } - (void)dealloc { - if (_responseStream != NULL) { - [self _closeResponseStream]; - CFRelease(_responseStream); - } - - CFRelease(_message); + if (_responseStream != NULL) { + [self _closeResponseStream]; + CFRelease(_responseStream); + } + + CFRelease(_message); } + (NSTimeInterval)defaultTimeoutInterval { - return 20.0; + return 20.0; } + (NSString *)defaultUserAgent { - static NSString *defaultUserAgent = nil; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; - NSString *appName = [infoDict objectForKey:@"CFBundleName"]; - NSString *shortVersion = [infoDict objectForKey:@"CFBundleShortVersionString"]; - NSString *bundleVersion = [infoDict objectForKey:@"CFBundleVersion"]; - - NSString *deviceName = nil; - NSString *systemName = nil; - NSString *systemVersion = nil; - + static NSString *defaultUserAgent = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; + NSString *appName = [infoDict objectForKey:@"CFBundleName"]; + NSString *shortVersion = [infoDict objectForKey:@"CFBundleShortVersionString"]; + NSString *bundleVersion = [infoDict objectForKey:@"CFBundleVersion"]; + + NSString *deviceName = nil; + NSString *systemName = nil; + NSString *systemVersion = nil; + #if TARGET_OS_IPHONE - - UIDevice *device = [UIDevice currentDevice]; - deviceName = [device model]; - systemName = [device systemName]; - systemVersion = [device systemVersion]; - + + UIDevice *device = [UIDevice currentDevice]; + deviceName = [device model]; + systemName = [device systemName]; + systemVersion = [device systemVersion]; + #else /* TARGET_OS_IPHONE */ - + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" - SInt32 versionMajor, versionMinor, versionBugFix; - Gestalt(gestaltSystemVersionMajor, &versionMajor); - Gestalt(gestaltSystemVersionMinor, &versionMinor); - Gestalt(gestaltSystemVersionBugFix, &versionBugFix); + SInt32 versionMajor, versionMinor, versionBugFix; + Gestalt(gestaltSystemVersionMajor, &versionMajor); + Gestalt(gestaltSystemVersionMinor, &versionMinor); + Gestalt(gestaltSystemVersionBugFix, &versionBugFix); #pragma clang diagnostic pop - - int mib[2] = { CTL_HW, HW_MODEL }; - size_t len = 0; - sysctl(mib, 2, NULL, &len, NULL, 0); - char *hw_model = malloc(len); - sysctl(mib, 2, hw_model, &len, NULL, 0); - deviceName = [NSString stringWithFormat:@"Macintosh %s", hw_model]; - free(hw_model); - - systemName = @"Mac OS X"; - systemVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix]; - + + int mib[2] = { CTL_HW, HW_MODEL }; + size_t len = 0; + sysctl(mib, 2, NULL, &len, NULL, 0); + char *hw_model = malloc(len); + sysctl(mib, 2, hw_model, &len, NULL, 0); + deviceName = [NSString stringWithFormat:@"Macintosh %s", hw_model]; + free(hw_model); + + systemName = @"Mac OS X"; + systemVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix]; + #endif /* TARGET_OS_IPHONE */ - - NSString *locale = [[NSLocale currentLocale] localeIdentifier]; - defaultUserAgent = [NSString stringWithFormat:@"%@ %@ build %@ (%@; %@ %@; %@)", appName, shortVersion, bundleVersion, deviceName, systemName, systemVersion, locale]; - }); - - return defaultUserAgent; + + NSString *locale = [[NSLocale currentLocale] localeIdentifier]; + defaultUserAgent = [NSString stringWithFormat:@"%@ %@ build %@ (%@; %@ %@; %@)", appName, shortVersion, bundleVersion, deviceName, systemName, systemVersion, locale]; + }); + + return defaultUserAgent; } - (void)_invokeCompletedBlock { - @synchronized(self) { - if (_completedBlock != NULL) { - _completedBlock(); + @synchronized(self) { + if (_completedBlock != NULL) { + _completedBlock(); + } } - } } - (void)_invokeProgressBlockWithDownloadProgress:(double)downloadProgress { - @synchronized(self) { - if (_progressBlock != NULL) { - _progressBlock(downloadProgress); + @synchronized(self) { + if (_progressBlock != NULL) { + _progressBlock(downloadProgress); + } } - } } - (void)_invokeDidReceiveResponseBlock { - @synchronized(self) { - if (_didReceiveResponseBlock != NULL) { - _didReceiveResponseBlock(); + @synchronized(self) { + if (_didReceiveResponseBlock != NULL) { + _didReceiveResponseBlock(); + } } - } } - (void)_invokeDidReceiveDataBlockWithData:(NSData *)data { - @synchronized(self) { - if (_didReceiveDataBlock != NULL) { - _didReceiveDataBlock(data); + @synchronized(self) { + if (_didReceiveDataBlock != NULL) { + _didReceiveDataBlock(data); + } } - } } - (void)_checkResponseContentLength { - if (_responseHeaders == nil) { - return; - } - - NSString *string = [_responseHeaders objectForKey:@"Content-Length"]; - if (string == nil) { - return; - } - - _responseContentLength = (NSUInteger)[string integerValue]; + if (_responseHeaders == nil) { + return; + } + + NSString *string = [_responseHeaders objectForKey:@"Content-Length"]; + if (string == nil) { + return; + } + if (remoteURL) { + NSString *etag = [_responseHeaders objectForKey:@"Etag"]; + if (!etag) { + etag = @""; + } + VerifyInfo *info = [VerifyInfo new]; + info.Etag = etag; + info.ContentLength = string; + [[DOUCacheManager shared] storeInfo:info forURL:remoteURL]; + } + _responseContentLength = (NSUInteger)[string integerValue]; } - (void)_readResponseHeaders { - if (_responseHeaders != nil) { - return; - } - - CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty(_responseStream, kCFStreamPropertyHTTPResponseHeader); - if (message == NULL) { - return; - } - - if (!CFHTTPMessageIsHeaderComplete(message)) { + if (_responseHeaders != nil) { + return; + } + + CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty(_responseStream, kCFStreamPropertyHTTPResponseHeader); + if (message == NULL) { + return; + } + + if (!CFHTTPMessageIsHeaderComplete(message)) { + CFRelease(message); + return; + } + + _responseHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(message)); + _statusCode = CFHTTPMessageGetResponseStatusCode(message); + _statusMessage = CFBridgingRelease(CFHTTPMessageCopyResponseStatusLine(message)); CFRelease(message); - return; - } - - _responseHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(message)); - _statusCode = CFHTTPMessageGetResponseStatusCode(message); - _statusMessage = CFBridgingRelease(CFHTTPMessageCopyResponseStatusLine(message)); - CFRelease(message); - - [self _checkResponseContentLength]; - [self _invokeDidReceiveResponseBlock]; + + [self _checkResponseContentLength]; + [self _invokeDidReceiveResponseBlock]; } - (void)_updateProgress { - double downloadProgress; - if (_responseContentLength == 0) { - if (_responseHeaders != nil) { - downloadProgress = 1.0; + double downloadProgress; + if (_responseContentLength == 0) { + if (_responseHeaders != nil) { + downloadProgress = 1.0; + } + else { + downloadProgress = 0.0; + } } else { - downloadProgress = 0.0; + downloadProgress = (double)_receivedLength / _responseContentLength; } - } - else { - downloadProgress = (double)_receivedLength / _responseContentLength; - } - - [self _invokeProgressBlockWithDownloadProgress:downloadProgress]; + + [self _invokeProgressBlockWithDownloadProgress:downloadProgress]; } - (void)_updateDownloadSpeed { - _downloadSpeed = _receivedLength / (CFAbsoluteTimeGetCurrent() - _startedTime); + _downloadSpeed = _receivedLength / (CFAbsoluteTimeGetCurrent() - _startedTime); } - (void)_closeResponseStream { - CFReadStreamClose(_responseStream); - CFReadStreamUnscheduleFromRunLoop(_responseStream, controller_get_runloop(), kCFRunLoopDefaultMode); - CFReadStreamSetClient(_responseStream, kCFStreamEventNone, NULL, NULL); + CFReadStreamClose(_responseStream); + CFReadStreamUnscheduleFromRunLoop(_responseStream, controller_get_runloop(), kCFRunLoopDefaultMode); + CFReadStreamSetClient(_responseStream, kCFStreamEventNone, NULL, NULL); } - (void)_responseStreamHasBytesAvailable { - [self _readResponseHeaders]; - - if (!CFReadStreamHasBytesAvailable(_responseStream)) { - return; - } - - CFIndex bufferSize; - if (_responseContentLength > 262144) { - bufferSize = 262144; - } - else if (_responseContentLength > 65536) { - bufferSize = 65536; - } - else { - bufferSize = 16384; - } - - UInt8 buffer[bufferSize]; - CFIndex bytesRead = CFReadStreamRead(_responseStream, buffer, bufferSize); - if (bytesRead < 0) { - [self _responseStreamErrorOccurred]; - return; - } - - if (bytesRead > 0) { - NSData *data = [NSData dataWithBytesNoCopy:buffer length:(NSUInteger)bytesRead freeWhenDone:NO]; - - @synchronized(self) { - if (_didReceiveDataBlock == NULL) { - if (_responseData == nil) { - _responseData = [NSMutableData data]; + [self _readResponseHeaders]; + + if (!CFReadStreamHasBytesAvailable(_responseStream)) { + return; + } + + CFIndex bufferSize; + if (_responseContentLength > 262144) { + bufferSize = 262144; + } + else if (_responseContentLength > 65536) { + bufferSize = 65536; + } + else { + bufferSize = 16384; + } + + UInt8 buffer[bufferSize]; + CFIndex bytesRead = CFReadStreamRead(_responseStream, buffer, bufferSize); + if (bytesRead < 0) { + [self _responseStreamErrorOccurred]; + return; + } + + if (bytesRead > 0) { + NSData *data = [NSData dataWithBytesNoCopy:buffer length:(NSUInteger)bytesRead freeWhenDone:NO]; + + @synchronized(self) { + if (_didReceiveDataBlock == NULL) { + if (_responseData == nil) { + _responseData = [NSMutableData data]; + } + + [_responseData appendData:data]; + } + else { + [self _invokeDidReceiveDataBlockWithData:data]; + } } - - [_responseData appendData:data]; - } - else { - [self _invokeDidReceiveDataBlockWithData:data]; - } + + _receivedLength += (unsigned long)bytesRead; + [self _updateProgress]; + [self _updateDownloadSpeed]; } - - _receivedLength += (unsigned long)bytesRead; - [self _updateProgress]; - [self _updateDownloadSpeed]; - } } - (void)_responseStrameEndEncountered { - [self _readResponseHeaders]; - [self _invokeProgressBlockWithDownloadProgress:1.0]; - [self _invokeCompletedBlock]; + [self _readResponseHeaders]; + [self _invokeProgressBlockWithDownloadProgress:1.0]; + [self _invokeCompletedBlock]; } - (void)_responseStreamErrorOccurred { - [self _readResponseHeaders]; - - _failed = YES; - [self _closeResponseStream]; - [self _invokeCompletedBlock]; + [self _readResponseHeaders]; + + _failed = YES; + [self _closeResponseStream]; + [self _invokeCompletedBlock]; } static void response_stream_client_callback(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo) { - @autoreleasepool { - __unsafe_unretained DOUSimpleHTTPRequest *request = (__bridge DOUSimpleHTTPRequest *)clientCallBackInfo; - - @synchronized(request) { - switch (type) { - case kCFStreamEventHasBytesAvailable: - [request _responseStreamHasBytesAvailable]; - break; - - case kCFStreamEventEndEncountered: - [request _responseStrameEndEncountered]; - break; - - case kCFStreamEventErrorOccurred: - [request _responseStreamErrorOccurred]; - break; - - default: - break; - } + @autoreleasepool { + __unsafe_unretained DOUSimpleHTTPRequest *request = (__bridge DOUSimpleHTTPRequest *)clientCallBackInfo; + + @synchronized(request) { + switch (type) { + case kCFStreamEventHasBytesAvailable: + [request _responseStreamHasBytesAvailable]; + break; + + case kCFStreamEventEndEncountered: + [request _responseStrameEndEncountered]; + break; + + case kCFStreamEventErrorOccurred: + [request _responseStreamErrorOccurred]; + break; + + default: + break; + } + } } - } } - (void)start { - if (_responseStream != NULL) { - return; - } - - CFHTTPMessageSetHeaderFieldValue(_message, CFSTR("User-Agent"), (__bridge CFStringRef)_userAgent); - if (_host != nil) { - CFHTTPMessageSetHeaderFieldValue(_message, CFSTR("Host"), (__bridge CFStringRef)_host); - } - - _responseStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, _message); - CFReadStreamSetProperty(_responseStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue); - CFReadStreamSetProperty(_responseStream, CFSTR("_kCFStreamPropertyReadTimeout"), (__bridge CFNumberRef)[NSNumber numberWithDouble:_timeoutInterval]); - CFReadStreamSetProperty(_responseStream, CFSTR("_kCFStreamPropertyWriteTimeout"), (__bridge CFNumberRef)[NSNumber numberWithDouble:_timeoutInterval]); - - CFStreamClientContext context; - bzero(&context, sizeof(context)); - context.info = (__bridge void *)self; - CFReadStreamSetClient(_responseStream, kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred, response_stream_client_callback, &context); - - CFReadStreamScheduleWithRunLoop(_responseStream, controller_get_runloop(), kCFRunLoopDefaultMode); - CFReadStreamOpen(_responseStream); - - _startedTime = CFAbsoluteTimeGetCurrent(); - _downloadSpeed = 0; + + + if (_responseStream != NULL) { + return; + } + + CFHTTPMessageSetHeaderFieldValue(_message, CFSTR("User-Agent"), (__bridge CFStringRef)_userAgent); + if (_host != nil) { + CFHTTPMessageSetHeaderFieldValue(_message, CFSTR("Host"), (__bridge CFStringRef)_host); + } + NSDictionary *header = [DOUCacheManager shared].customHeader; + if (header) { + NSArray* keys = header.keyEnumerator.allObjects; + for (NSString *key in keys) { + NSString *value = header[key]; + if (value) { + CFStringRef cfKey = (__bridge CFStringRef)key; + CFStringRef cfValue = (__bridge CFStringRef)value; + CFHTTPMessageSetHeaderFieldValue(_message, cfKey, cfValue); + } + } + } + + _responseStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, _message); + CFReadStreamSetProperty(_responseStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue); + CFReadStreamSetProperty(_responseStream, CFSTR("_kCFStreamPropertyReadTimeout"), (__bridge CFNumberRef)[NSNumber numberWithDouble:_timeoutInterval]); + CFReadStreamSetProperty(_responseStream, CFSTR("_kCFStreamPropertyWriteTimeout"), (__bridge CFNumberRef)[NSNumber numberWithDouble:_timeoutInterval]); + + CFStreamClientContext context; + bzero(&context, sizeof(context)); + context.info = (__bridge void *)self; + CFReadStreamSetClient(_responseStream, kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred, response_stream_client_callback, &context); + + CFReadStreamScheduleWithRunLoop(_responseStream, controller_get_runloop(), kCFRunLoopDefaultMode); + CFReadStreamOpen(_responseStream); + + _startedTime = CFAbsoluteTimeGetCurrent(); + _downloadSpeed = 0; } - (void)cancel { - if (_responseStream == NULL || _failed) { - return; - } - - __block CFTypeRef __request = CFBridgingRetain(self); - CFRunLoopPerformBlock(controller_get_runloop(), kCFRunLoopDefaultMode, ^{ - @autoreleasepool { - [(__bridge DOUSimpleHTTPRequest *)__request _closeResponseStream]; - CFBridgingRelease(__request); + if (_responseStream == NULL || _failed) { + return; } - }); + + __block CFTypeRef __request = CFBridgingRetain(self); + CFRunLoopPerformBlock(controller_get_runloop(), kCFRunLoopDefaultMode, ^{ + @autoreleasepool { + [(__bridge DOUSimpleHTTPRequest *)__request _closeResponseStream]; + CFBridgingRelease(__request); + } + }); } - (NSString *)responseString { - if (_responseData == nil) { - return nil; - } - - if (_responseString == nil) { - _responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding]; - } - - return _responseString; + if (_responseData == nil) { + return nil; + } + + if (_responseString == nil) { + _responseString = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding]; + } + + return _responseString; } @end diff --git a/src/DOUTrack.h b/src/DOUTrack.h new file mode 100755 index 0000000..8a01173 --- /dev/null +++ b/src/DOUTrack.h @@ -0,0 +1,22 @@ +/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */ +/* + * DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac: + * + * https://github.com/douban/DOUAudioStreamer + * + * Copyright 2013-2014 Douban Inc. All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the LICENSE file for full text. + * + * Authors: + * Chongyu Zhu + * + */ + +#import +#import "DOUAudioFile.h" + +@interface DOUTrack : NSObject +@property (nonatomic, strong) NSURL *audioFileURL; +@end diff --git a/src/DOUTrack.m b/src/DOUTrack.m new file mode 100755 index 0000000..b3412fd --- /dev/null +++ b/src/DOUTrack.m @@ -0,0 +1,21 @@ +/* vim: set ft=objc fenc=utf-8 sw=2 ts=2 et: */ +/* + * DOUAudioStreamer - A Core Audio based streaming audio player for iOS/Mac: + * + * https://github.com/douban/DOUAudioStreamer + * + * Copyright 2013-2014 Douban Inc. All rights reserved. + * + * Use and distribution licensed under the BSD license. See + * the LICENSE file for full text. + * + * Authors: + * Chongyu Zhu + * + */ + +#import "DOUTrack.h" + +@implementation DOUTrack + +@end