diff --git a/android/modules/media/src/java/ti/modules/titanium/media/AudioPlayerProxy.java b/android/modules/media/src/java/ti/modules/titanium/media/AudioPlayerProxy.java index cf0d2bd50c5..f72da504576 100644 --- a/android/modules/media/src/java/ti/modules/titanium/media/AudioPlayerProxy.java +++ b/android/modules/media/src/java/ti/modules/titanium/media/AudioPlayerProxy.java @@ -183,20 +183,29 @@ public boolean isPaused() return false; } - // An alias for play so that @Kroll.method public void start() { - play(); + TiSound s = getSound(); + if (s != null) { + s.play(); + } + } + + @Kroll.method + public void restart() + { + stop(); + start(); } @Kroll.method public void play() { - TiSound s = getSound(); - if (s != null) { - s.play(); - } + Log.w( + TAG, + "The \"play()\" method has been deprecated in favor of the cross-platform \"start()\" method in Titanium 7.4.0."); + start(); } @Kroll.method @@ -243,6 +252,31 @@ public int getAudioSessionId() return 0; } + // clang-format off + @Kroll.method + @Kroll.getProperty + public boolean getMuted() + // clang-format on + { + TiSound s = getSound(); + if (s != null) { + return s.isMuted(); + } + return false; + } + + // clang-format off + @Kroll.method + @Kroll.setProperty + public void setMuted(boolean muted) + // clang-format on + { + TiSound s = getSound(); + if (s != null) { + s.setMuted(muted); + } + } + // clang-format off @Kroll.method @Kroll.getProperty diff --git a/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java b/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java index 43a3ad56d48..74f2d3e7ec9 100644 --- a/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java +++ b/android/modules/media/src/java/ti/modules/titanium/media/MediaModule.java @@ -185,6 +185,27 @@ public class MediaModule extends KrollModule implements Handler.Callback @Kroll.constant public static final int CAMERA_FLASH_AUTO = 2; + @Kroll.constant + public static final int AUDIO_STATE_BUFFERING = 0; // current playback is in the buffering from the network state + @Kroll.constant + public static final int AUDIO_STATE_INITIALIZED = 1; // current playback is in the initialization state + @Kroll.constant + public static final int AUDIO_STATE_PAUSED = 2; // current playback is in the paused state + @Kroll.constant + public static final int AUDIO_STATE_PLAYING = 3; // current playback is in the playing state + @Kroll.constant + public static final int AUDIO_STATE_STARTING = 4; // current playback is in the starting playback state + @Kroll.constant + public static final int AUDIO_STATE_STOPPED = 5; // current playback is in the stopped state + @Kroll.constant + public static final int AUDIO_STATE_STOPPING = 6; // current playback is in the stopping state + @Kroll.constant + public static final int AUDIO_STATE_WAITING_FOR_DATA = + 7; // current playback is in the waiting for audio data from the network state + @Kroll.constant + public static final int AUDIO_STATE_WAITING_FOR_QUEUE = + 8; // current playback is in the waiting for audio data to fill the queue state + private static String mediaType = MEDIA_TYPE_PHOTO; private static String extension = ".jpg"; private TiTempFileHelper tempFileHelper; diff --git a/android/modules/media/src/java/ti/modules/titanium/media/TiSound.java b/android/modules/media/src/java/ti/modules/titanium/media/TiSound.java index 42726b43b32..1eed779bc37 100644 --- a/android/modules/media/src/java/ti/modules/titanium/media/TiSound.java +++ b/android/modules/media/src/java/ti/modules/titanium/media/TiSound.java @@ -426,6 +426,22 @@ public int getDuration() return duration; } + public boolean isMuted() + { + AudioManager audioManager = + (AudioManager) TiApplication.getInstance().getApplicationContext().getSystemService(Context.AUDIO_SERVICE); + + return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT; + } + + public void setMuted(boolean muted) + { + if (mp != null) { + float scale = muted ? 0.0f : 1.0f; + mp.setVolume(scale, scale); + } + } + public int getTime() { int time = 0; diff --git a/apidoc/Titanium/Media/AudioPlayer.yml b/apidoc/Titanium/Media/AudioPlayer.yml index 63b5a318c5c..5ce5af4381b 100644 --- a/apidoc/Titanium/Media/AudioPlayer.yml +++ b/apidoc/Titanium/Media/AudioPlayer.yml @@ -5,10 +5,14 @@ summary: | description: | On Android, when you are done playing a given audio file, you must call the [release](Titanium.Media.AudioPlayer.release) method to stop buffering audio data and - release associated system resources. + release associated system resources. Since 7.4.0, this method is available on iOS as well + and will release all audio-player related resources. After this method has been called, + the object should not be accessed anymore. On iOS, you can control how the audio stream interacts with other system sounds - by setting . + by setting . Since Titanium 7.4.0, this API + uses the [AVPlayer API](https://developer.apple.com/documentation/avfoundation/avplayer) for a more modern + and performant audio playback. Use the method to create an audio player. extends: Titanium.Proxy @@ -51,16 +55,13 @@ examples: allowBackground: true }); - startStopButton.addEventListener('click',function() { + startStopButton.addEventListener('click', function() { // When paused, playing returns false. // If both are false, playback is stopped. if (audioPlayer.playing || audioPlayer.paused) { audioPlayer.stop(); pauseResumeButton.enabled = false; - if (Ti.Platform.name === 'android') - { - audioPlayer.release(); - } + audioPlayer.release(); } else { audioPlayer.start(); pauseResumeButton.enabled = true; @@ -83,12 +84,9 @@ examples: Ti.API.info('State: ' + e.description + ' (' + e.state + ')'); }); - win.addEventListener('close',function() { + win.addEventListener('close', function() { audioPlayer.stop(); - if (Ti.Platform.osname === 'android') - { - audioPlayer.release(); - } + audioPlayer.release(); }); win.open(); @@ -131,8 +129,11 @@ methods: - name: play summary: Starts or resumes audio playback. + deprecated: + since: "7.4.0" + notes: Use the cross-platform API [start](Titanium.Media.AudioPlayer.start) instead. description: | - This method is identical to [start](Titanium.Media.AudioPlayer.start). + This method is identical to . platforms: [android] - name: setPaused @@ -145,14 +146,36 @@ methods: - name: paused summary: Pass `true` to pause the current playback temporarily, `false` to unpause it. type: Boolean + deprecated: + since: "7.4.0" + notes: Use the cross-platform API [pause](Titanium.Media.AudioPlayer.pause) instead. + platforms: [iphone, ipad] + + - name: seekToTime + parameters: + - name: time + summary: The time in milliseconds to seek to. + type: Number + summary: | + Moves the playback cursor and invokes the specified block when the seek + operation has either been completed or been interrupted. + description: | + Use this method to seek to a specified time for the current player item and + to be notified by the `seek` event when the seek operation is complete. platforms: [iphone, ipad] + since: "7.4.0" - name: release summary: Stops buffering audio data and releases audio resources. description: | On Android, this method should be called when you are done streaming a given audio object, to release underlying resources, including buffered data. - platforms: [android] + + On iOS, this method can be called for parity to release the current player + instance and it's observers. Note: If you are listening to the `progress` event, + you should remove and re-add the event listener. + platforms: [android, iphone, ipad] + since: { "android": "0.9", "iphone": "7.4.0", "ipad": "7.4.0" } - name: getAudioSessionId summary: Returns the audio session id. @@ -163,6 +186,10 @@ methods: - name: start summary: Starts or resumes audio playback. + + - name: restart + summary: Restarts (stops and stars) audio playback. + since: "7.4.0" - name: stateDescription summary: | @@ -187,7 +214,7 @@ events: - name: state summary: Current state of playback. type: Number - constants: Titanium.Media.AudioPlayer.STATE_* + constants: Titanium.Media.AUDIO_STATE_* - name: description summary: Text description of the state of playback. @@ -211,8 +238,21 @@ events: was generated by the operating system, that system's error value is used. Otherwise, this value will be -1. type: Number - platforms: [android] + platforms: [android, iphone, ipad] + since: {"android": "0.9", "iphone": "7.4.0", "ipad": "7.4.0"} + - name: metadata + summary: Fired when the timed metadata was encountered most recently within the media as it plays. + description: | + Use this event to receive meta information about real time streams and buffers, for example + the title of an active radio stream. Read more about timed metadata in the [Apple docs](https://developer.apple.com/documentation/avfoundation/avplayeritem/1389602-timedmetadata?language=objc). + properties: + - name: items + summary: An array of metadata items containing relevant information about the current media item. + type: Array + platforms: [iphone, ipad] + since: "7.4.0" + - name: error summary: Fired when there's an error. properties: @@ -231,51 +271,89 @@ events: properties: - name: progress summary: Current progress, in milliseconds. + + - name: seek + summary: Fired once the [seekToTime](Titanium.Media.AudioPlayer.seek) method completes. + properties: + - name: finished + summary: | + The event for any prior seek request that is still in process will be invoked + immediately with the `finished` parameter set to `false`. + If the new request completes without being interrupted by another seek + request or by any other operation this event will be invoked with + the `finished` parameter set to `true`. properties: - name: STATE_BUFFERING summary: Audio data is being buffered from the network. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_INITIALIZED summary: Audio playback is being initialized. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_PAUSED summary: Playback is paused. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_PLAYING summary: Audio playback is active. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_STARTING summary: Audio playback is starting. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_STOPPED summary: Audio playback is stopped. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_STOPPING summary: Audio playback is stopping. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_WAITING_FOR_DATA summary: Player is waiting for audio data from the network. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: STATE_WAITING_FOR_QUEUE summary: Player is waiting for audio data to fill the queue. type: Number permission: read-only + deprecated: + since: "7.4.0" + notes: Use instead. - name: AUDIO_TYPE_ALARM summary: Used to identify the volume of audio streams for alarms. @@ -372,6 +450,45 @@ properties: permission: read-only platforms: [iphone, ipad] + - name: muted + summary: Indicates whether or not audio output of the player is muted. + description: Only affects audio muting for the player instance and not for the device. + platforms: [iphone, ipad, android] + type: Boolean + since: "7.4.0" + + - name: externalPlaybackActive + summary: Indicates whether the player is currently playing video in "external playback" mode. + platforms: [iphone, ipad] + type: Boolean + since: "7.4.0" + permission: read-only + + - name: allowsExternalPlayback + summary: Indicates whether the player allows switching to "external playback" mode. + default: true + type: Boolean + since: "7.4.0" + platforms: [iphone, ipad] + + - name: rate + summary: | + Indicates the desired rate of playback; 0.0 means "paused", 1.0 indicates a + desire to play at the natural rate of the current item. In addition, 2.0 + would mean that the audio plays twice as fast. + description: | + Setting the value of rate to 0.0 pauses playback, causing the value of + the `state` property change to . + Setting the rate to a non-zero value causes the value of the `state` property + to become either or + , depending on whether sufficient + media data has been buffered for playback to occur and whether the player's + default behavior of waiting in order to minimize stalling is permitted. + default: 0.0 + type: Number + since: "7.4.0" + platforms: [iphone, ipad] + - name: paused summary: Boolean indicating if audio playback is paused. type: Boolean @@ -394,7 +511,7 @@ properties: - name: state summary: Current state of playback, specified using one of the `STATE` constants defined on this object. type: Number - constants: Titanium.Media.AudioPlayer.STATE_* + constants: Titanium.Media.AUDIO_STATE_* permission: read-only platforms: [iphone, ipad] @@ -426,7 +543,10 @@ properties: platforms: [iphone, ipad] - name: bufferSize - summary: Size of the buffer used for streaming, in bytes. + summary: Size of the buffer used for streaming, in milliseconds. + description: | + Prior to Titanium 7.4.0, this property was set in bytes. Since the internal iOS + API moved to the more modern AVPlayer, this property now works with milliseconds instead. type: Number platforms: [iphone, ipad] @@ -437,3 +557,25 @@ properties: type: Number since: "3.3.0" platforms: [android] + +--- +name: TiMetadataItemType +summary: An abstract type to represent a metadata item inside the `metadata` event (iOS only). +description: | + This type is key-value based and is returned inside an array of metadata items. + Read more about timed metadata items in the [Apple docs](https://developer.apple.com/documentation/avfoundation/avmetadataitem?language=objc). +platforms: [iphone, ipad] +since: "7.4.0" +properties: + - name: key + summary: The key of the metadata item, e.g. "title". + type: String + - name: keySpace + summary: The key-path of the metadata item. + type: String + - name: value + summary: The value of the metadata item. Can be represented as various types. + type: [String, Number, Boolean] + - name: extraAttributes + summary: A dictionary of the additional attributes. + type: Dictionary diff --git a/apidoc/Titanium/Media/Media.yml b/apidoc/Titanium/Media/Media.yml index 0f678597d46..b6de9bf9308 100644 --- a/apidoc/Titanium/Media/Media.yml +++ b/apidoc/Titanium/Media/Media.yml @@ -887,6 +887,61 @@ properties: type: Number permission: read-only platforms: [iphone, ipad] + + - name: AUDIO_STATE_BUFFERING + summary: Audio data is being buffered from the network. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_INITIALIZED + summary: Audio playback is being initialized. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_PAUSED + summary: Playback is paused. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_PLAYING + summary: Audio playback is active. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_STARTING + summary: Audio playback is starting. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_STOPPED + summary: Audio playback is stopped. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_STOPPING + summary: Audio playback is stopping. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_WAITING_FOR_DATA + summary: Player is waiting for audio data from the network. + type: Number + permission: read-only + since: "7.4.0" + + - name: AUDIO_STATE_WAITING_FOR_QUEUE + summary: Player is waiting for audio data to fill the queue. + type: Number + permission: read-only + since: "7.4.0" + - name: CAMERA_FLASH_AUTO summary: Constant specifying to have the device determine to use the flash or not. type: Number diff --git a/iphone/Classes/AudioStreamer/AudioStreamer.h b/iphone/Classes/AudioStreamer/AudioStreamer.h deleted file mode 100755 index f24ebc4c8eb..00000000000 --- a/iphone/Classes/AudioStreamer/AudioStreamer.h +++ /dev/null @@ -1,144 +0,0 @@ -// -// AudioStreamer.h -// StreamingAudioPlayer -// -// Created by Matt Gallagher on 27/09/08. -// Copyright 2008 Matt Gallagher. All rights reserved. -// -// Permission is given to use this source code file, free of charge, in any -// project, commercial or otherwise, entirely at your risk, with the condition -// that any redistribution (in part or whole) of source code must retain -// this copyright and permission notice. Attribution in compiled projects is -// appreciated but not required. -// - -// This file is meant to be a repository for common defintions between AudioStreamerBC (backcompat, 3.1.x) -// and AudioStreamerCUR (iOS 3.2+), as well as a proxy which shunts messages to the appropriate AudioStreamer. -// - SPT - -// Also note that we've had to change enumeration and class names here - this is because -// some modules may require the use of AudioStreamer in external libraries and the -// symbols cannot be changed on that end. The use of common symbols in Titanium without -// namespaces is a recurring problem, and we can thank Objective-C for it. -// - SPT - -#if defined(USE_TI_MEDIASYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETSYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAAUDIOPLAYER) || \ - defined(USE_TI_MEDIAGETAUDIOPLAYER) - -#define LOG_QUEUED_BUFFERS 0 - -#define kNumAQBufs 16 // Number of audio queue buffers we allocate. - // Needs to be big enough to keep audio pipeline - // busy (non-zero number of queued buffers) but - // not so big that audio takes too long to begin - // (kNumAQBufs * kAQBufSize of data must be - // loaded before playback will start). - // Set LOG_QUEUED_BUFFERS to 1 to log how many - // buffers are queued at any time -- if it drops - // to zero too often, this value may need to - // increase. Min 3, typical 8-24. - -#define kAQMaxPacketDescs 512 // Number of packet descriptions in our array -#define kAQDefaultBufSize 2048 // Number of bytes in each audio queue buffer - // Needs to be big enough to hold a packet of - // audio from the audio file. If number is too - // large, queuing of audio before playback starts - // will take too long. - // Highly compressed files can use smaller - // numbers (512 or less). 2048 should hold all - // but the largest packets. A buffer size error - // will occur if this number is too small. - - -typedef enum -{ - AS_INITIALIZED = 0, - AS_STARTING_FILE_THREAD, - AS_WAITING_FOR_DATA, - AS_WAITING_FOR_QUEUE_TO_START, - AS_PLAYING, - AS_BUFFERING, - AS_STOPPING, - AS_STOPPED, - AS_PAUSED, - AS_FLUSHING_EOF -} AudioStreamerState; - -typedef enum -{ - AS_NO_STOP = 0, - AS_STOPPING_EOF, - AS_STOPPING_USER_ACTION, - AS_STOPPING_ERROR, - AS_STOPPING_TEMPORARILY -} AudioStreamerStopReason; - -typedef enum -{ - AS_NO_ERROR = 0, - AS_NETWORK_CONNECTION_FAILED, - AS_FILE_STREAM_GET_PROPERTY_FAILED, - AS_FILE_STREAM_SEEK_FAILED, - AS_FILE_STREAM_PARSE_BYTES_FAILED, - AS_FILE_STREAM_OPEN_FAILED, - AS_FILE_STREAM_CLOSE_FAILED, - AS_AUDIO_DATA_NOT_FOUND, - AS_AUDIO_QUEUE_CREATION_FAILED, - AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED, - AS_AUDIO_QUEUE_ENQUEUE_FAILED, - AS_AUDIO_QUEUE_ADD_LISTENER_FAILED, - AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED, - AS_AUDIO_QUEUE_START_FAILED, - AS_AUDIO_QUEUE_PAUSE_FAILED, - AS_AUDIO_QUEUE_BUFFER_MISMATCH, - AS_AUDIO_QUEUE_DISPOSE_FAILED, - AS_AUDIO_QUEUE_STOP_FAILED, - AS_AUDIO_QUEUE_FLUSH_FAILED, - AS_AUDIO_STREAMER_FAILED, - AS_GET_AUDIO_TIME_FAILED, - AS_AUDIO_BUFFER_TOO_SMALL -} AudioStreamerErrorCode; - -extern NSString * const ASStatusChangedNotification; - -@protocol AudioStreamerDelegate --(void)playbackStateChanged:(id)sender; --(void)errorReceived:(id)sender; -@end - - -@protocol AudioStreamerProtocol -@property AudioStreamerErrorCode errorCode; -@property (nonatomic, readonly) AudioStreamerState state; -@property (readonly) double progress; -@property (readonly) double duration; -@property (readwrite) UInt32 bitRate; -@property (readwrite) double volume; -@property (readwrite,assign) id delegate; -@property (nonatomic,readwrite,assign) UInt32 bufferSize; - -- (void)start; -- (void)stop; -- (void)pause; -- (BOOL)isPlaying; -- (BOOL)isPaused; -- (BOOL)isWaiting; -- (BOOL)isIdle; -@end - -@interface AudioStreamer : NSObject -{ - id streamer; - id delegate; -} - -- (id)initWithURL:(NSURL *)aURL; -+ (NSString*)stringForErrorCode:(AudioStreamerErrorCode)code; - -@end - -#endif diff --git a/iphone/Classes/AudioStreamer/AudioStreamer.m b/iphone/Classes/AudioStreamer/AudioStreamer.m deleted file mode 100755 index 7afc02f7659..00000000000 --- a/iphone/Classes/AudioStreamer/AudioStreamer.m +++ /dev/null @@ -1,192 +0,0 @@ -// -// AudioStreamer.m -// StreamingAudioPlayer -// -// Created by Matt Gallagher on 27/09/08. -// Copyright 2008 Matt Gallagher. All rights reserved. -// -// Permission is given to use this source code file, free of charge, in any -// project, commercial or otherwise, entirely at your risk, with the condition -// that any redistribution (in part or whole) of source code must retain -// this copyright and permission notice. Attribution in compiled projects is -// appreciated but not required. -// - -#if defined(USE_TI_MEDIASYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETSYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAAUDIOPLAYER) || \ - defined(USE_TI_MEDIAGETAUDIOPLAYER) - -#import "AudioStreamer.h" -#import "AudioStreamerCUR.h" -#import "TiUtils.h" - -NSString * const ASStatusChangedNotification = @"ASStatusChangedNotification"; - -static NSString * const AS_NO_ERROR_STRING = @"No error."; -static NSString * const AS_FILE_STREAM_GET_PROPERTY_FAILED_STRING = @"File stream get property failed."; -static NSString * const AS_FILE_STREAM_SEEK_FAILED_STRING = @"File stream seek failed."; -static NSString * const AS_FILE_STREAM_PARSE_BYTES_FAILED_STRING = @"Parse bytes failed."; -static NSString * const AS_FILE_STREAM_OPEN_FAILED_STRING = @"Open audio file stream failed."; -static NSString * const AS_FILE_STREAM_CLOSE_FAILED_STRING = @"Close audio file stream failed."; -static NSString * const AS_AUDIO_QUEUE_CREATION_FAILED_STRING = @"Audio queue creation failed."; -static NSString * const AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED_STRING = @"Audio buffer allocation failed."; -static NSString * const AS_AUDIO_QUEUE_ENQUEUE_FAILED_STRING = @"Queueing of audio buffer failed."; -static NSString * const AS_AUDIO_QUEUE_ADD_LISTENER_FAILED_STRING = @"Audio queue add listener failed."; -static NSString * const AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED_STRING = @"Audio queue remove listener failed."; -static NSString * const AS_AUDIO_QUEUE_START_FAILED_STRING = @"Audio queue start failed."; -static NSString * const AS_AUDIO_QUEUE_BUFFER_MISMATCH_STRING = @"Audio queue buffers don't match."; -static NSString * const AS_AUDIO_QUEUE_DISPOSE_FAILED_STRING = @"Audio queue dispose failed."; -static NSString * const AS_AUDIO_QUEUE_PAUSE_FAILED_STRING = @"Audio queue pause failed."; -static NSString * const AS_AUDIO_QUEUE_STOP_FAILED_STRING = @"Audio queue stop failed."; -static NSString * const AS_AUDIO_DATA_NOT_FOUND_STRING = @"No audio data found."; -static NSString * const AS_AUDIO_QUEUE_FLUSH_FAILED_STRING = @"Audio queue flush failed."; -static NSString * const AS_GET_AUDIO_TIME_FAILED_STRING = @"Audio queue get current time failed."; -static NSString * const AS_AUDIO_STREAMER_FAILED_STRING = @"Audio playback failed"; -static NSString * const AS_NETWORK_CONNECTION_FAILED_STRING = @"Network connection failed"; -static NSString * const AS_AUDIO_BUFFER_TOO_SMALL_STRING = @"Audio packets are larger than kAQBufSize."; - - -@implementation AudioStreamer -@synthesize delegate; - -- (id)initWithURL:(NSURL *)aURL -{ - self = [super init]; - if (self != nil) - { - streamer = [[AudioStreamerCUR alloc] initWithURL:aURL]; - [streamer setDelegate:self]; - } - return self; -} - -- (void)dealloc -{ - [streamer setDelegate:nil]; - RELEASE_TO_NIL(streamer); - [super dealloc]; -} - --(void)playbackStateChanged:(id)sender; -{ - [delegate playbackStateChanged:self]; -} - --(void)errorReceived:(id)sender -{ - [delegate errorReceived:self]; -} -// -// stringForErrorCode: -// -// Converts an error code to a string that can be localized or presented -// to the user. -// -// Parameters: -// anErrorCode - the error code to convert -// -// returns the string representation of the error code -// -+ (NSString *)stringForErrorCode:(AudioStreamerErrorCode)anErrorCode -{ - switch (anErrorCode) - { - case AS_NO_ERROR: - return AS_NO_ERROR_STRING; - case AS_FILE_STREAM_GET_PROPERTY_FAILED: - return AS_FILE_STREAM_GET_PROPERTY_FAILED_STRING; - case AS_FILE_STREAM_SEEK_FAILED: - return AS_FILE_STREAM_SEEK_FAILED_STRING; - case AS_FILE_STREAM_PARSE_BYTES_FAILED: - return AS_FILE_STREAM_PARSE_BYTES_FAILED_STRING; - case AS_AUDIO_QUEUE_CREATION_FAILED: - return AS_AUDIO_QUEUE_CREATION_FAILED_STRING; - case AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED: - return AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED_STRING; - case AS_AUDIO_QUEUE_ENQUEUE_FAILED: - return AS_AUDIO_QUEUE_ENQUEUE_FAILED_STRING; - case AS_AUDIO_QUEUE_ADD_LISTENER_FAILED: - return AS_AUDIO_QUEUE_ADD_LISTENER_FAILED_STRING; - case AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED: - return AS_AUDIO_QUEUE_REMOVE_LISTENER_FAILED_STRING; - case AS_AUDIO_QUEUE_START_FAILED: - return AS_AUDIO_QUEUE_START_FAILED_STRING; - case AS_AUDIO_QUEUE_BUFFER_MISMATCH: - return AS_AUDIO_QUEUE_BUFFER_MISMATCH_STRING; - case AS_FILE_STREAM_OPEN_FAILED: - return AS_FILE_STREAM_OPEN_FAILED_STRING; - case AS_FILE_STREAM_CLOSE_FAILED: - return AS_FILE_STREAM_CLOSE_FAILED_STRING; - case AS_AUDIO_QUEUE_DISPOSE_FAILED: - return AS_AUDIO_QUEUE_DISPOSE_FAILED_STRING; - case AS_AUDIO_QUEUE_PAUSE_FAILED: - return AS_AUDIO_QUEUE_DISPOSE_FAILED_STRING; - case AS_AUDIO_QUEUE_FLUSH_FAILED: - return AS_AUDIO_QUEUE_FLUSH_FAILED_STRING; - case AS_AUDIO_DATA_NOT_FOUND: - return AS_AUDIO_DATA_NOT_FOUND_STRING; - case AS_GET_AUDIO_TIME_FAILED: - return AS_GET_AUDIO_TIME_FAILED_STRING; - case AS_NETWORK_CONNECTION_FAILED: - return AS_NETWORK_CONNECTION_FAILED_STRING; - case AS_AUDIO_QUEUE_STOP_FAILED: - return AS_AUDIO_QUEUE_STOP_FAILED_STRING; - case AS_AUDIO_STREAMER_FAILED: - return AS_AUDIO_STREAMER_FAILED_STRING; - case AS_AUDIO_BUFFER_TOO_SMALL: - return AS_AUDIO_BUFFER_TOO_SMALL_STRING; - default: - return AS_AUDIO_STREAMER_FAILED_STRING; - } - - return AS_AUDIO_STREAMER_FAILED_STRING; -} - -#define RUN_ON_STREAMER_SET(func, type) \ --(void)func:(type)arg \ -{\ - [streamer func:arg];\ -} - -#define RUN_ON_STREAMER_RETURN(func, type) \ --(type)func\ -{\ - return [streamer func];\ -} - -#define RUN_ON_STREAMER(func)\ --(void)func\ -{\ - [streamer func];\ -} - -// Properties -RUN_ON_STREAMER_SET(setErrorCode,AudioStreamerErrorCode) -RUN_ON_STREAMER_SET(setBitRate,UInt32) -RUN_ON_STREAMER_SET(setBufferSize, UInt32) -RUN_ON_STREAMER_SET(setVolume, double) - -RUN_ON_STREAMER_RETURN(errorCode, AudioStreamerErrorCode) -RUN_ON_STREAMER_RETURN(bitRate, UInt32) -RUN_ON_STREAMER_RETURN(state, AudioStreamerState) -RUN_ON_STREAMER_RETURN(progress, double) -RUN_ON_STREAMER_RETURN(bufferSize, UInt32) -RUN_ON_STREAMER_RETURN(volume, double) -RUN_ON_STREAMER_RETURN(duration, NSTimeInterval); - -// Functions -RUN_ON_STREAMER_RETURN(isPlaying, BOOL) -RUN_ON_STREAMER_RETURN(isPaused, BOOL) -RUN_ON_STREAMER_RETURN(isWaiting, BOOL) -RUN_ON_STREAMER_RETURN(isIdle, BOOL) - -RUN_ON_STREAMER(start) -RUN_ON_STREAMER(pause) -RUN_ON_STREAMER(stop) -@end - - -#endif diff --git a/iphone/Classes/AudioStreamer/AudioStreamerCUR.h b/iphone/Classes/AudioStreamer/AudioStreamerCUR.h deleted file mode 100644 index 80affb53b20..00000000000 --- a/iphone/Classes/AudioStreamer/AudioStreamerCUR.h +++ /dev/null @@ -1,119 +0,0 @@ -// -// AudioStreamer.h -// StreamingAudioPlayer -// -// Created by Matt Gallagher on 27/09/08. -// Copyright 2008 Matt Gallagher. All rights reserved. -// -// Permission is given to use this source code file, free of charge, in any -// project, commercial or otherwise, entirely at your risk, with the condition -// that any redistribution (in part or whole) of source code must retain -// this copyright and permission notice. Attribution in compiled projects is -// appreciated but not required. -// - -#if defined(USE_TI_MEDIASYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETSYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAAUDIOPLAYER) || \ - defined(USE_TI_MEDIAGETAUDIOPLAYER) - -#ifdef TARGET_OS_IPHONE -#import -#else -#import -#endif //TARGET_OS_IPHONE - -#import "AudioStreamer.h" -#include -#include - -@interface AudioStreamerCUR : NSObject -{ - NSURL *url; - id delegate; - - // - // Special threading consideration: - // The audioQueue property should only ever be accessed inside a - // synchronized(self) block and only *after* checking that ![self isFinishing] - // - AudioQueueRef audioQueue; - AudioFileStreamID audioFileStream; // the audio file stream parser - AudioStreamBasicDescription asbd; // description of the audio - NSThread *internalThread; // the thread where the download and - // audio file stream parsing occurs - - AudioQueueBufferRef audioQueueBuffer[kNumAQBufs]; // audio queue buffers - AudioStreamPacketDescription packetDescs[kAQMaxPacketDescs]; // packet descriptions for enqueuing audio - unsigned int fillBufferIndex; // the index of the audioQueueBuffer that is being filled - UInt32 packetBufferSize; - UInt32 bytesFilled; // how many bytes have been filled - UInt32 packetsFilled; // how many packets have been filled - bool inuse[kNumAQBufs]; // flags to indicate that a buffer is still in use - NSInteger buffersUsed; - NSDictionary *httpHeaders; - - AudioStreamerState state; - AudioStreamerStopReason stopReason; - AudioStreamerErrorCode errorCode; - OSStatus err; - - bool discontinuous; // flag to indicate middle of the stream - - pthread_mutex_t queueBuffersMutex; // a mutex to protect the inuse flags - pthread_cond_t queueBufferReadyCondition; // a condition varable for handling the inuse flags - - CFReadStreamRef stream; - - UInt32 bitRate; // Bits per second in the file - /*Titanium Modification begin*/ - //NSInteger dataOffset; // Offset of the first audio packet in the stream - //NSInteger fileLength; // Length of the file in bytes - //NSInteger seekByteOffset; // Seek offset within the file in bytes - UInt64 dataOffset; - UInt64 fileLength; - UInt64 seekByteOffset; - /*Titanium Modifications End*/ - UInt64 audioDataByteCount; // Used when the actual number of audio bytes in - // the file is known (more accurate than assuming - // the whole file is audio) - UInt32 bufferSize; // Dynamic size of the buffer (buffer is default size of 2k if unspec'd) - - UInt64 processedPacketsCount; // number of packets accumulated for bitrate estimation - UInt64 processedPacketsSizeTotal; // byte size of accumulated estimation packets - - double volume; - double seekTime; - BOOL seekWasRequested; - double requestedSeekTime; - double sampleRate; // Sample rate of the file (used to compare with - // samples played by the queue for current playback - // time) - double packetDuration; // sample rate times frames per packet - double lastProgress; // last calculated progress point -} - -@property AudioStreamerErrorCode errorCode; -@property (nonatomic, readonly) AudioStreamerState state; -@property (readonly) double progress; -@property (readonly) double duration; -@property (readwrite) UInt32 bitRate; -@property (readonly) NSDictionary *httpHeaders; -@property (nonatomic,readwrite,assign) UInt32 bufferSize; - -- (id)initWithURL:(NSURL *)aURL; -- (void)start; -- (void)stop; -- (void)pause; -- (BOOL)isPlaying; -- (BOOL)isPaused; -- (BOOL)isWaiting; -- (BOOL)isIdle; -- (void)seekToTime:(double)newSeekTime; -- (double)calculatedBitRate; - -@end - -#endif diff --git a/iphone/Classes/AudioStreamer/AudioStreamerCUR.m b/iphone/Classes/AudioStreamer/AudioStreamerCUR.m deleted file mode 100644 index c1bb6ac50e5..00000000000 --- a/iphone/Classes/AudioStreamer/AudioStreamerCUR.m +++ /dev/null @@ -1,1827 +0,0 @@ -// -// AudioStreamer.m -// StreamingAudioPlayer -// -// Created by Matt Gallagher on 27/09/08. -// Copyright 2008 Matt Gallagher. All rights reserved. -// -// Permission is given to use this source code file, free of charge, in any -// project, commercial or otherwise, entirely at your risk, with the condition -// that any redistribution (in part or whole) of source code must retain -// this copyright and permission notice. Attribution in compiled projects is -// appreciated but not required. -// - -#if defined(USE_TI_MEDIASYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETSYSTEMMUSICPLAYER) || \ - defined(USE_TI_MEDIAGETAPPMUSICPLAYER) || \ - defined(USE_TI_MEDIAAUDIOPLAYER) || \ - defined(USE_TI_MEDIAGETAUDIOPLAYER) - -#import "AudioStreamerCUR.h" -#ifdef TARGET_OS_IPHONE -#import -#endif - -#define BitRateEstimationMaxPackets 5000 -#define BitRateEstimationMinPackets 50 - -@interface AudioStreamerCUR () -@property (nonatomic, readwrite) AudioStreamerState state; - -- (void)handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream - fileStreamPropertyID:(AudioFileStreamPropertyID)inPropertyID - ioFlags:(UInt32 *)ioFlags; -- (void)handleAudioPackets:(const void *)inInputData - numberBytes:(UInt32)inNumberBytes - numberPackets:(UInt32)inNumberPackets - packetDescriptions:(AudioStreamPacketDescription *)inPacketDescriptions; -- (void)handleBufferCompleteForQueue:(AudioQueueRef)inAQ - buffer:(AudioQueueBufferRef)inBuffer; -- (void)handlePropertyChangeForQueue:(AudioQueueRef)inAQ - propertyID:(AudioQueuePropertyID)inID; - -#ifdef TARGET_OS_IPHONE -- (void)handleInterruptionChangeToState:(AudioQueuePropertyID)inInterruptionState; -#endif - -- (void)internalSeekToTime:(double)newSeekTime; -- (void)enqueueBuffer; -- (void)handleReadFromStream:(CFReadStreamRef)aStream - eventType:(CFStreamEventType)eventType; - -@end - -#pragma mark Audio Callback Function Prototypes - -static void MyAudioQueueOutputCallbackCUR(void* inClientData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer); -static void MyAudioQueueIsRunningCallbackCUR(void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID); -static void MyPropertyListenerProcCUR( void * inClientData, - AudioFileStreamID inAudioFileStream, - AudioFileStreamPropertyID inPropertyID, - UInt32 * ioFlags); -static void MyPacketsProcCUR( void * inClientData, - UInt32 inNumberBytes, - UInt32 inNumberPackets, - const void * inInputData, - AudioStreamPacketDescription *inPacketDescriptions); -OSStatus MyEnqueueBufferCUR(AudioStreamerCUR* myData); - - -#pragma mark Audio Callback Function Implementations - -// -// MyPropertyListenerProc -// -// Receives notification when the AudioFileStream has audio packets to be -// played. In response, this function creates the AudioQueue, getting it -// ready to begin playback (playback won't begin until audio packets are -// sent to the queue in MyEnqueueBuffer). -// -// This function is adapted from Apple's example in AudioFileStreamExample with -// kAudioQueueProperty_IsRunning listening added. -// -void MyPropertyListenerProcCUR( void * inClientData, - AudioFileStreamID inAudioFileStream, - AudioFileStreamPropertyID inPropertyID, - UInt32 * ioFlags) -{ - // this is called by audio file stream when it finds property values - AudioStreamerCUR* streamer = (AudioStreamerCUR *)inClientData; - [streamer - handlePropertyChangeForFileStream:inAudioFileStream - fileStreamPropertyID:inPropertyID - ioFlags:ioFlags]; -} - -// -// MyPacketsProc -// -// When the AudioStream has packets to be played, this function gets an -// idle audio buffer and copies the audio packets into it. The calls to -// MyEnqueueBuffer won't return until there are buffers available (or the -// playback has been stopped). -// -// This function is adapted from Apple's example in AudioFileStreamExample with -// CBR functionality added. -// -void MyPacketsProcCUR( void * inClientData, - UInt32 inNumberBytes, - UInt32 inNumberPackets, - const void * inInputData, - AudioStreamPacketDescription *inPacketDescriptions) -{ - // this is called by audio file stream when it finds packets of audio - AudioStreamerCUR* streamer = (AudioStreamerCUR *)inClientData; - [streamer - handleAudioPackets:inInputData - numberBytes:inNumberBytes - numberPackets:inNumberPackets - packetDescriptions:inPacketDescriptions]; -} - -// -// MyAudioQueueOutputCallback -// -// Called from the AudioQueue when playback of specific buffers completes. This -// function signals from the AudioQueue thread to the AudioStream thread that -// the buffer is idle and available for copying data. -// -// This function is unchanged from Apple's example in AudioFileStreamExample. -// -void MyAudioQueueOutputCallbackCUR( void* inClientData, - AudioQueueRef inAQ, - AudioQueueBufferRef inBuffer) -{ - // this is called by the audio queue when it has finished decoding our data. - // The buffer is now free to be reused. - AudioStreamerCUR* streamer = (AudioStreamerCUR*)inClientData; - [streamer handleBufferCompleteForQueue:inAQ buffer:inBuffer]; -} - -// -// MyAudioQueueIsRunningCallback -// -// Called from the AudioQueue when playback is started or stopped. This -// information is used to toggle the observable "isPlaying" property and -// set the "finished" flag. -// -void MyAudioQueueIsRunningCallbackCUR(void *inUserData, AudioQueueRef inAQ, AudioQueuePropertyID inID) -{ - AudioStreamerCUR* streamer = (AudioStreamerCUR *)inUserData; - [streamer handlePropertyChangeForQueue:inAQ propertyID:inID]; -} - - - -#pragma mark CFReadStream Callback Function Implementations - -// -// ReadStreamCallBack -// -// This is the callback for the CFReadStream from the network connection. This -// is where all network data is passed to the AudioFileStream. -// -// Invoked when an error occurs, the stream ends or we have data to read. -// -static void ASReadStreamCallBackCUR -( - CFReadStreamRef aStream, - CFStreamEventType eventType, - void* inClientInfo -) -{ - AudioStreamerCUR* streamer = (AudioStreamerCUR *)inClientInfo; - [streamer handleReadFromStream:aStream eventType:eventType]; -} - -@implementation AudioStreamerCUR - -@synthesize errorCode; -@synthesize state; -@synthesize bitRate; -@synthesize httpHeaders; -@synthesize delegate; -@synthesize bufferSize; - --(UInt32)bufferSize -{ - return (bufferSize) ? bufferSize : kAQDefaultBufSize; -} - -// -// initWithURL -// -// Init method for the object. -// -- (id)initWithURL:(NSURL *)aURL -{ - self = [super init]; - if (self != nil) - { - url = [aURL retain]; - bufferSize = 0; - volume = 1.0; - } - return self; -} - -// -// dealloc -// -// Releases instance memory. -// -- (void)dealloc -{ - [self stop]; - [url release]; - [super dealloc]; -} - -// -// isFinishing -// -// returns YES if the audio has reached a stopping condition. -// -- (BOOL)isFinishing -{ - @synchronized (self) - { - if ((errorCode != AS_NO_ERROR && state != AS_INITIALIZED) || - ((state == AS_STOPPING || state == AS_STOPPED) && - stopReason != AS_STOPPING_TEMPORARILY)) - { - return YES; - } - } - - return NO; -} - -// -// runLoopShouldExit -// -// returns YES if the run loop should exit. -// -- (BOOL)runLoopShouldExit -{ - @synchronized(self) - { - if (errorCode != AS_NO_ERROR || - (state == AS_STOPPED && - stopReason != AS_STOPPING_TEMPORARILY)) - { - return YES; - } - } - - return NO; -} - -// -// failWithErrorCode: -// -// Sets the playback state to failed and logs the error. -// -// Parameters: -// anErrorCode - the error condition -// -- (void)failWithErrorCode:(AudioStreamerErrorCode)anErrorCode -{ - @synchronized(self) - { - if (errorCode != AS_NO_ERROR) - { - // Only set the error once. - return; - } - - errorCode = anErrorCode; - - if (err) - { - char *errChars = (char *)&err; - NSLog(@"%@ err: %c%c%c%c %d\n", - [AudioStreamer stringForErrorCode:anErrorCode], - errChars[3], errChars[2], errChars[1], errChars[0], - (int)err); - } - else - { - NSLog(@"%@", [AudioStreamer stringForErrorCode:anErrorCode]); - } - - if (state == AS_PLAYING || - state == AS_PAUSED || - state == AS_BUFFERING) - { - self.state = AS_STOPPING; - stopReason = AS_STOPPING_ERROR; - AudioQueueStop(audioQueue, true); - } - - [delegate errorReceived:self]; - } -} - -// -// setState: -// -// Sets the state and sends a notification that the state has changed. -// -// This method -// -// Parameters: -// anErrorCode - the error condition -// -- (void)setState:(AudioStreamerState)aStatus -{ - @synchronized(self) - { - if (state != aStatus) - { - state = aStatus; - [delegate playbackStateChanged:self]; - } - } -} - -// -// isPlaying -// -// returns YES if the audio currently playing. -// -- (BOOL)isPlaying -{ - if (state == AS_PLAYING) - { - return YES; - } - - return NO; -} - -// -// isPaused -// -// returns YES if the audio currently playing. -// -- (BOOL)isPaused -{ - if (state == AS_PAUSED) - { - return YES; - } - - return NO; -} - -// -// isWaiting -// -// returns YES if the AudioStreamer is waiting for a state transition of some -// kind. -// -- (BOOL)isWaiting -{ - @synchronized(self) - { - if ([self isFinishing] || - state == AS_STARTING_FILE_THREAD|| - state == AS_WAITING_FOR_DATA || - state == AS_WAITING_FOR_QUEUE_TO_START || - state == AS_BUFFERING) - { - return YES; - } - } - - return NO; -} - -// -// isIdle -// -// returns YES if the AudioStream is in the AS_INITIALIZED state (i.e. -// isn't doing anything). -// -- (BOOL)isIdle -{ - if (state == AS_INITIALIZED) - { - return YES; - } - - return NO; -} - -// -// hintForFileExtension: -// -// Generates a first guess for the file type based on the file's extension -// -// Parameters: -// fileExtension - the file extension -// -// returns a file type hint that can be passed to the AudioFileStream -// -+ (AudioFileTypeID)hintForFileExtension:(NSString *)fileExtension -{ - AudioFileTypeID fileTypeHint = kAudioFileMP3Type; - if ([fileExtension isEqual:@"mp3"]) - { - fileTypeHint = kAudioFileMP3Type; - } - else if ([fileExtension isEqual:@"wav"]) - { - fileTypeHint = kAudioFileWAVEType; - } - else if ([fileExtension isEqual:@"aifc"]) - { - fileTypeHint = kAudioFileAIFCType; - } - else if ([fileExtension isEqual:@"aiff"]) - { - fileTypeHint = kAudioFileAIFFType; - } - else if ([fileExtension isEqual:@"m4a"]) - { - fileTypeHint = kAudioFileM4AType; - } - else if ([fileExtension isEqual:@"mp4"]) - { - fileTypeHint = kAudioFileMPEG4Type; - } - else if ([fileExtension isEqual:@"caf"]) - { - fileTypeHint = kAudioFileCAFType; - } - else if ([fileExtension isEqual:@"aac"]) - { - fileTypeHint = kAudioFileAAC_ADTSType; - } - return fileTypeHint; -} - -// -// openReadStream -// -// Open the audioFileStream to parse data and the fileHandle as the data -// source. -// -- (BOOL)openReadStream -{ - @synchronized(self) - { - NSAssert([[NSThread currentThread] isEqual:internalThread], - @"File stream download must be started on the internalThread"); - NSAssert(stream == nil, @"Download stream already initialized"); - - //check to see if url is local file - if (![url isFileURL]) { - // - // Create the HTTP GET request - // - CFHTTPMessageRef message= CFHTTPMessageCreateRequest(NULL, (CFStringRef)@"GET", (CFURLRef)url, kCFHTTPVersion1_1); - - // - // If we are creating this request to seek to a location, set the - // requested byte range in the headers. - // - if (fileLength > 0 && seekByteOffset > 0) - { - CFHTTPMessageSetHeaderFieldValue(message, CFSTR("Range"), - (CFStringRef)[NSString stringWithFormat:@"bytes=%ld-%ld", (long)seekByteOffset, (long)fileLength]); - discontinuous = YES; - } - - // - // Create the read stream that will receive data from the HTTP request - // - stream = CFReadStreamCreateForHTTPRequest(NULL, message); - CFRelease(message); - - // - // Enable stream redirection - // - if (CFReadStreamSetProperty( - stream, - kCFStreamPropertyHTTPShouldAutoredirect, - kCFBooleanTrue) == false) - { - [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; - return NO; - } - - // - // Handle SSL connections - // - if( [[url absoluteString] rangeOfString:@"https"].location != NSNotFound ) - { - /*---Titanium Modifications start---*/ - /* - * kCFStreamSSLAllowsExpiredCertificates, kCFStreamSSLAllowsExpiredRoots, kCFStreamSSLValidatesCertificateChain - * deprecated in iOS4. Use kCFStreamSSLValidatesCertificateChain to disable certificate chain validation. - NSDictionary *sslSettings = - [NSDictionary dictionaryWithObjectsAndKeys: - (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, - [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates, - [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredRoots, - [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, - [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, - [NSNull null], kCFStreamSSLPeerName, - nil]; - */ - NSDictionary *sslSettings = - [NSDictionary dictionaryWithObjectsAndKeys: - (NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL, kCFStreamSSLLevel, - [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain, - [NSNull null], kCFStreamSSLPeerName, - nil]; - /*---Titanium Modifications End---*/ - CFReadStreamSetProperty(stream, kCFStreamPropertySSLSettings, sslSettings); - } - } - else { - //File is local - stream = CFReadStreamCreateWithFile(NULL, (CFURLRef)url); - } - // - // We're now ready to receive data - // - self.state = AS_WAITING_FOR_DATA; - - // - // Open the stream - // - if (!CFReadStreamOpen(stream)) - { - CFRelease(stream); - [self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED]; - return NO; - } - - // - // Set our callback function to receive the data - // - CFStreamClientContext context = {0, self, NULL, NULL, NULL}; - CFReadStreamSetClient( - stream, - kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, - ASReadStreamCallBackCUR, - &context); - CFReadStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); - } - - return YES; -} - -// -// startInternal -// -// This is the start method for the AudioStream thread. This thread is created -// because it will be blocked when there are no audio buffers idle (and ready -// to receive audio data). -// -// Activity in this thread: -// - Creation and cleanup of all AudioFileStream and AudioQueue objects -// - Receives data from the CFReadStream -// - AudioFileStream processing -// - Copying of data from AudioFileStream into audio buffers -// - Stopping of the thread because of end-of-file -// - Stopping due to error or failure -// -// Activity *not* in this thread: -// - AudioQueue playback and notifications (happens in AudioQueue thread) -// - Actual download of NSURLConnection data (NSURLConnection's thread) -// - Creation of the AudioStreamer (other, likely "main" thread) -// - Invocation of -start method (other, likely "main" thread) -// - User/manual invocation of -stop (other, likely "main" thread) -// -// This method contains bits of the "main" function from Apple's example in -// AudioFileStreamExample. -// -- (void)startInternal -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - @synchronized(self) - { - if (state != AS_STARTING_FILE_THREAD) - { - if (state != AS_STOPPING && - state != AS_STOPPED) - { - NSLog(@"### Not starting audio thread. State code is: %u", state); - } - self.state = AS_INITIALIZED; - [pool release]; - return; - } - - // initialize a mutex and condition so that we can block on buffers in use. - pthread_mutex_init(&queueBuffersMutex, NULL); - pthread_cond_init(&queueBufferReadyCondition, NULL); - - if (![self openReadStream]) - { - goto cleanup; - } - } - - // - // Process the run loop until playback is finished or failed. - // - BOOL isRunning = YES; - do - { - isRunning = [[NSRunLoop currentRunLoop] - runMode:NSDefaultRunLoopMode - beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25]]; - - @synchronized(self) { - if (seekWasRequested) { - [self internalSeekToTime:requestedSeekTime]; - seekWasRequested = NO; - } - } - - // - // If there are no queued buffers, we need to check here since the - // handleBufferCompleteForQueue:buffer: should not change the state - // (may not enter the synchronized section). - // - if (buffersUsed == 0 && self.state == AS_PLAYING) - { - err = AudioQueuePause(audioQueue); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_PAUSE_FAILED]; - return; - } - self.state = AS_BUFFERING; - } - } while (isRunning && ![self runLoopShouldExit]); - -cleanup: - - @synchronized(self) - { - // - // Cleanup the read stream if it is still open - // - if (stream) - { - CFReadStreamClose(stream); - CFRelease(stream); - stream = nil; - } - - // - // Close the audio file strea, - // - if (audioFileStream) - { - err = AudioFileStreamClose(audioFileStream); - audioFileStream = nil; - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_CLOSE_FAILED]; - } - } - - // - // Dispose of the Audio Queue - // - if (audioQueue) - { - err = AudioQueueDispose(audioQueue, true); - audioQueue = nil; - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_DISPOSE_FAILED]; - } - } - - pthread_mutex_destroy(&queueBuffersMutex); - pthread_cond_destroy(&queueBufferReadyCondition); - - [httpHeaders release]; - httpHeaders = nil; - - bytesFilled = 0; - packetsFilled = 0; - seekByteOffset = 0; - packetBufferSize = 0; - self.state = AS_INITIALIZED; - - [internalThread release]; - internalThread = nil; - } - - [pool release]; -} - -// -// start -// -// Calls startInternal in a new thread. -// -- (void)start -{ - @synchronized (self) - { - if (state == AS_PAUSED) - { - [self pause]; - } - else if (state == AS_INITIALIZED) - { - NSAssert([[NSThread currentThread] isEqual:[NSThread mainThread]], - @"Playback can only be started from the main thread."); - self.state = AS_STARTING_FILE_THREAD; - internalThread = - [[NSThread alloc] - initWithTarget:self - selector:@selector(startInternal) - object:nil]; - [internalThread start]; - } - } -} - - -// internalSeekToTime: -// -// Called from our internal runloop to reopen the stream at a seeked location -// -- (void)internalSeekToTime:(double)newSeekTime -{ - if ([self calculatedBitRate] == 0.0 || fileLength <= 0) - { - return; - } - - // - // Calculate the byte offset for seeking - // - seekByteOffset = dataOffset + - (newSeekTime / self.duration) * (fileLength - dataOffset); - - // - // Attempt to leave 1 useful packet at the end of the file (although in - // reality, this may still seek too far if the file has a long trailer). - // - if (seekByteOffset > fileLength - 2 * packetBufferSize) - { - seekByteOffset = fileLength - 2 * packetBufferSize; - } - - // - // Store the old time from the audio queue and the time that we're seeking - // to so that we'll know the correct time progress after seeking. - // - seekTime = newSeekTime; - - // - // Attempt to align the seek with a packet boundary - // - double calculatedBitRate = [self calculatedBitRate]; - if (packetDuration > 0 && - calculatedBitRate > 0) - { - UInt32 ioFlags = 0; - SInt64 packetAlignedByteOffset; - SInt64 seekPacket = floor(newSeekTime / packetDuration); - err = AudioFileStreamSeek(audioFileStream, seekPacket, &packetAlignedByteOffset, &ioFlags); - if (!err && !(ioFlags & kAudioFileStreamSeekFlag_OffsetIsEstimated)) - { - seekTime -= ((seekByteOffset - dataOffset) - packetAlignedByteOffset) * 8.0 / calculatedBitRate; - seekByteOffset = packetAlignedByteOffset + dataOffset; - } - } - - // - // Close the current read straem - // - if (stream) - { - CFReadStreamClose(stream); - CFRelease(stream); - stream = nil; - } - - // - // Stop the audio queue - // - self.state = AS_STOPPING; - stopReason = AS_STOPPING_TEMPORARILY; - err = AudioQueueStop(audioQueue, true); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_STOP_FAILED]; - return; - } - - // - // Re-open the file stream. It will request a byte-range starting at - // seekByteOffset. - // - [self openReadStream]; -} - -// -// seekToTime: -// -// Attempts to seek to the new time. Will be ignored if the bitrate or fileLength -// are unknown. -// -// Parameters: -// newTime - the time to seek to -// -- (void)seekToTime:(double)newSeekTime -{ - @synchronized(self) - { - seekWasRequested = YES; - requestedSeekTime = newSeekTime; - } -} - -// -// progress -// -// returns the current playback progress. Will return zero if sampleRate has -// not yet been detected. -// -- (double)progress -{ - @synchronized(self) - { - if (sampleRate > 0 && ![self isFinishing]) - { - if (state != AS_PLAYING && state != AS_PAUSED && state != AS_BUFFERING) - { - return lastProgress * 1000; - } - - AudioTimeStamp queueTime; - Boolean discontinuity; - err = AudioQueueGetCurrentTime(audioQueue, NULL, &queueTime, &discontinuity); - - const OSStatus AudioQueueStopped = 0x73746F70; // 0x73746F70 is 'stop' - if (err == AudioQueueStopped) - { - return lastProgress * 1000; - } - else if (err) - { - [self failWithErrorCode:AS_GET_AUDIO_TIME_FAILED]; - } - - double progress = seekTime + queueTime.mSampleTime / sampleRate; - if (progress < 0.0) - { - progress = 0.0; - } - - lastProgress = progress; - } - } - - return lastProgress * 1000; -} - - -// -// volume -// -// returns the current playback volume. -// -- (double)volume -{ - @synchronized(self) - { - if ((audioQueue != nil) && ![self isFinishing]) - { - AudioQueueParameterValue result; - OSStatus error = AudioQueueGetParameter(audioQueue,kAudioQueueParam_Volume,&result); - if (error == noErr) - { - volume = (double)result; - } - else { - NSLog(@"[WARN] An error %u occurred while fetching the volume of a stream.",(unsigned int)error); - } - } - } - return volume; -} - -// -// setVolume -// -// sets the current playback volume. -// -- (void)setVolume:(double)value -{ - volume = value; - @synchronized(self) - { - if ((audioQueue != nil) && ![self isFinishing]) - { - OSStatus error = AudioQueueSetParameter(audioQueue,kAudioQueueParam_Volume,(AudioQueueParameterValue)value); - if (error != noErr) { - NSLog(@"[WARN] An error %u occurred while setting the volume of a stream.",(unsigned int)error); - } - } - } -} - - -// -// calculatedBitRate -// -// returns the bit rate, if known. Uses packet duration times running bits per -// packet if available, otherwise it returns the nominal bitrate. Will return -// zero if no useful option available. -// -- (double)calculatedBitRate -{ - if (packetDuration && processedPacketsCount > BitRateEstimationMinPackets) - { - double averagePacketByteSize = processedPacketsSizeTotal / processedPacketsCount; - return 8.0 * averagePacketByteSize / packetDuration; - } - - if (bitRate) - { - return (double)bitRate; - } - - return 0; -} - -// -// duration -// -// Calculates the duration of available audio from the bitRate and fileLength. -// -// returns the calculated duration in seconds. -// -- (double)duration -{ - double calculatedBitRate = [self calculatedBitRate]; - - if (calculatedBitRate == 0 || fileLength == 0) - { - return 0.0; - } - - return (fileLength - dataOffset) / (calculatedBitRate * 0.125); -} - -// -// pause -// -// A togglable pause function. -// -- (void)pause -{ - @synchronized(self) - { - if (state == AS_PLAYING) - { - err = AudioQueuePause(audioQueue); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_PAUSE_FAILED]; - return; - } - self.state = AS_PAUSED; - } - else if (state == AS_PAUSED) - { - AudioQueuePrime(audioQueue, 1, NULL); - err = AudioQueueStart(audioQueue, NULL); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED]; - return; - } - self.state = AS_PLAYING; - } - } -} - -// -// stop -// -// This method can be called to stop downloading/playback before it completes. -// It is automatically called when an error occurs. -// -// If playback has not started before this method is called, it will toggle the -// "isPlaying" property so that it is guaranteed to transition to true and -// back to false -// -- (void)stop -{ - @synchronized(self) - { - if (audioQueue && - (state == AS_PLAYING || state == AS_PAUSED || - state == AS_BUFFERING || state == AS_WAITING_FOR_QUEUE_TO_START)) - { - self.state = AS_STOPPING; - stopReason = AS_STOPPING_USER_ACTION; - err = AudioQueueStop(audioQueue, true); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_STOP_FAILED]; - return; - } - } - else if (state != AS_INITIALIZED) - { - self.state = AS_STOPPED; - stopReason = AS_STOPPING_USER_ACTION; - } - seekWasRequested = NO; - } - - while (state != AS_INITIALIZED) - { - [NSThread sleepForTimeInterval:0.1]; - } -} - -// -// handleReadFromStream:eventType: -// -// Reads data from the network file stream into the AudioFileStream -// -// Parameters: -// aStream - the network file stream -// eventType - the event which triggered this method -// -- (void)handleReadFromStream:(CFReadStreamRef)aStream - eventType:(CFStreamEventType)eventType -{ - if (aStream != stream) - { - // - // Ignore messages from old streams - // - return; - } - - if (eventType == kCFStreamEventErrorOccurred) - { - [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; - } - else if (eventType == kCFStreamEventEndEncountered) - { - @synchronized(self) - { - if ([self isFinishing]) - { - return; - } - } - - // - // If there is a partially filled buffer, pass it to the AudioQueue for - // processing - // - if (bytesFilled) - { - if (self.state == AS_WAITING_FOR_DATA) - { - // - // Force audio data smaller than one whole buffer to play. - // - self.state = AS_FLUSHING_EOF; - } - [self enqueueBuffer]; - } - - @synchronized(self) - { - if (state == AS_WAITING_FOR_DATA) - { - [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; - } - - // - // We left the synchronized section to enqueue the buffer so we - // must check that we are !finished again before touching the - // audioQueue - // - else if (![self isFinishing]) - { - if (audioQueue) - { - // - // Set the progress at the end of the stream - // - err = AudioQueueFlush(audioQueue); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; - return; - } - - self.state = AS_STOPPING; - stopReason = AS_STOPPING_EOF; - err = AudioQueueStop(audioQueue, false); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_FLUSH_FAILED]; - return; - } - } - else - { - self.state = AS_STOPPED; - stopReason = AS_STOPPING_EOF; - } - } - } - } - else if (eventType == kCFStreamEventHasBytesAvailable) - { - if (!httpHeaders && ![url isFileURL]) - { - CFTypeRef message = - CFReadStreamCopyProperty(stream, kCFStreamPropertyHTTPResponseHeader); - httpHeaders = - (NSDictionary *)CFHTTPMessageCopyAllHeaderFields((CFHTTPMessageRef)message); - CFRelease(message); - - // - // Only read the content length if we seeked to time zero, otherwise - // we only have a subset of the total bytes. - // - if (seekByteOffset == 0) - { - fileLength = [[httpHeaders objectForKey:@"Content-Length"] integerValue]; - } - } - - if (!audioFileStream) - { - // - // Attempt to guess the file type from the URL. Reading the MIME type - // from the httpHeaders might be a better approach since lots of - // URL's don't have the right extension. - // - // If you have a fixed file-type, you may want to hardcode this. - // - AudioFileTypeID fileTypeHint = - [AudioStreamerCUR hintForFileExtension:[[url path] pathExtension]]; - - // create an audio file stream parser - err = AudioFileStreamOpen(self, MyPropertyListenerProcCUR, MyPacketsProcCUR, - fileTypeHint, &audioFileStream); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_OPEN_FAILED]; - return; - } - } - - UInt8 bytes[[self bufferSize]]; - CFIndex length; - @synchronized(self) - { - if ([self isFinishing] || !CFReadStreamHasBytesAvailable(stream)) - { - return; - } - - // - // Read the bytes from the stream - // - length = CFReadStreamRead(stream, bytes, [self bufferSize]); - - if (length == -1) - { - [self failWithErrorCode:AS_AUDIO_DATA_NOT_FOUND]; - return; - } - - if (length == 0) - { - return; - } - } - - if (discontinuous) - { - err = AudioFileStreamParseBytes(audioFileStream, (UInt32)length, bytes, kAudioFileStreamParseFlag_Discontinuity); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; - return; - } - } - else - { - err = AudioFileStreamParseBytes(audioFileStream, (UInt32)length, bytes, 0); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED]; - return; - } - } - } -} - -// -// enqueueBuffer -// -// Called from MyPacketsProc and connectionDidFinishLoading to pass filled audio -// bufffers (filled by MyPacketsProc) to the AudioQueue for playback. This -// function does not return until a buffer is idle for further filling or -// the AudioQueue is stopped. -// -// This function is adapted from Apple's example in AudioFileStreamExample with -// CBR functionality added. -// -- (void)enqueueBuffer -{ - @synchronized(self) - { - if ([self isFinishing] || stream == 0) - { - return; - } - - inuse[fillBufferIndex] = true; // set in use flag - buffersUsed++; - - // enqueue buffer - AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; - fillBuf->mAudioDataByteSize = bytesFilled; - - if (packetsFilled) - { - err = AudioQueueEnqueueBuffer(audioQueue, fillBuf, packetsFilled, packetDescs); - } - else - { - err = AudioQueueEnqueueBuffer(audioQueue, fillBuf, 0, NULL); - } - - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_ENQUEUE_FAILED]; - return; - } - - - if (state == AS_BUFFERING || - state == AS_WAITING_FOR_DATA || - state == AS_FLUSHING_EOF || - (state == AS_STOPPED && stopReason == AS_STOPPING_TEMPORARILY)) - { - // - // Fill all the buffers before starting. This ensures that the - // AudioFileStream stays a small amount ahead of the AudioQueue to - // avoid an audio glitch playing streaming files on iPhone SDKs < 3.0 - // - if (state == AS_FLUSHING_EOF || buffersUsed == kNumAQBufs - 1) - { - if (self.state == AS_BUFFERING) - { - AudioQueuePrime(audioQueue, 1, NULL); - err = AudioQueueStart(audioQueue, NULL); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED]; - return; - } - self.state = AS_PLAYING; - } - else - { - self.state = AS_WAITING_FOR_QUEUE_TO_START; - - AudioQueuePrime(audioQueue, 1, NULL); - err = AudioQueueStart(audioQueue, NULL); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_START_FAILED]; - return; - } - } - } - } - - // go to next buffer - if (++fillBufferIndex >= kNumAQBufs) fillBufferIndex = 0; - bytesFilled = 0; // reset bytes filled - packetsFilled = 0; // reset packets filled - } - - // wait until next buffer is not in use - pthread_mutex_lock(&queueBuffersMutex); - while (inuse[fillBufferIndex]) - { - pthread_cond_wait(&queueBufferReadyCondition, &queueBuffersMutex); - } - pthread_mutex_unlock(&queueBuffersMutex); -} - -// -// createQueue -// -// Method to create the AudioQueue from the parameters gathered by the -// AudioFileStream. -// -// Creation is deferred to the handling of the first audio packet (although -// it could be handled any time after kAudioFileStreamProperty_ReadyToProducePackets -// is true). -// -- (void)createQueue -{ - sampleRate = asbd.mSampleRate; - packetDuration = asbd.mFramesPerPacket / sampleRate; - - // create the audio queue - err = AudioQueueNewOutput(&asbd, MyAudioQueueOutputCallbackCUR, self, NULL, NULL, 0, &audioQueue); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_CREATION_FAILED]; - return; - } - - // set the volume - err = AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, (AudioQueueParameterValue)volume); - - // start the queue if it has not been started already - // listen to the "isRunning" property - err = AudioQueueAddPropertyListener(audioQueue, kAudioQueueProperty_IsRunning, MyAudioQueueIsRunningCallbackCUR, self); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_ADD_LISTENER_FAILED]; - return; - } - - // get the packet size if it is available - UInt32 sizeOfUInt32 = sizeof(UInt32); - err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_PacketSizeUpperBound, &sizeOfUInt32, &packetBufferSize); - if (err || packetBufferSize == 0) - { - err = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MaximumPacketSize, &sizeOfUInt32, &packetBufferSize); - if (err || packetBufferSize == 0) - { - // No packet size available, just use the default - packetBufferSize = [self bufferSize]; - } - } - - // allocate audio queue buffers - for (unsigned int i = 0; i < kNumAQBufs; ++i) - { - err = AudioQueueAllocateBuffer(audioQueue, packetBufferSize, &audioQueueBuffer[i]); - if (err) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_BUFFER_ALLOCATION_FAILED]; - return; - } - } - - // get the cookie size - UInt32 cookieSize; - Boolean writable; - OSStatus ignorableError; - ignorableError = AudioFileStreamGetPropertyInfo(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, &writable); - if (ignorableError) - { - return; - } - - // get the cookie data - void* cookieData = calloc(1, cookieSize); - ignorableError = AudioFileStreamGetProperty(audioFileStream, kAudioFileStreamProperty_MagicCookieData, &cookieSize, cookieData); - if (ignorableError) - { - return; - } - - // set the cookie on the queue. - ignorableError = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, cookieData, cookieSize); - free(cookieData); - if (ignorableError) - { - return; - } -} - -// -// handlePropertyChangeForFileStream:fileStreamPropertyID:ioFlags: -// -// Object method which handles implementation of MyPropertyListenerProc -// -// Parameters: -// inAudioFileStream - should be the same as self->audioFileStream -// inPropertyID - the property that changed -// ioFlags - the ioFlags passed in -// -- (void)handlePropertyChangeForFileStream:(AudioFileStreamID)inAudioFileStream - fileStreamPropertyID:(AudioFileStreamPropertyID)inPropertyID - ioFlags:(UInt32 *)ioFlags -{ - @synchronized(self) - { - if ([self isFinishing]) - { - return; - } - - if (inPropertyID == kAudioFileStreamProperty_ReadyToProducePackets) - { - discontinuous = true; - } - else if (inPropertyID == kAudioFileStreamProperty_DataOffset) - { - SInt64 offset; - UInt32 offsetSize = sizeof(offset); - err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataOffset, &offsetSize, &offset); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; - return; - } - dataOffset = offset; - - if (audioDataByteCount) - { - fileLength = dataOffset + audioDataByteCount; - } - } - else if (inPropertyID == kAudioFileStreamProperty_AudioDataByteCount) - { - UInt32 byteCountSize = sizeof(UInt64); - err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_AudioDataByteCount, &byteCountSize, &audioDataByteCount); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; - return; - } - fileLength = dataOffset + audioDataByteCount; - } - else if (inPropertyID == kAudioFileStreamProperty_DataFormat) - { - if (asbd.mSampleRate == 0) - { - UInt32 asbdSize = sizeof(asbd); - - // get the stream format. - err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &asbdSize, &asbd); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; - return; - } - } - } - else if (inPropertyID == kAudioFileStreamProperty_FormatList) - { - Boolean outWriteable; - UInt32 formatListSize; - err = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, &outWriteable); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; - return; - } - - AudioFormatListItem *formatList = malloc(formatListSize); - if (formatList == NULL) { - [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; - return; - } - err = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_FormatList, &formatListSize, formatList); - if (err) - { - [self failWithErrorCode:AS_FILE_STREAM_GET_PROPERTY_FAILED]; - free(formatList); - return; - } - - for (int i = 0; i * sizeof(AudioFormatListItem) < formatListSize; i += sizeof(AudioFormatListItem)) - { - AudioStreamBasicDescription pasbd = formatList[i].mASBD; - - if (pasbd.mFormatID == kAudioFormatMPEG4AAC_HE) - { - // - // We've found HE-AAC, remember this to tell the audio queue - // when we construct it. - // -#if !TARGET_IPHONE_SIMULATOR - asbd = pasbd; -#endif - break; - } - } - free(formatList); - } - else - { -// NSLog(@"Property is %c%c%c%c", -// ((char *)&inPropertyID)[3], -// ((char *)&inPropertyID)[2], -// ((char *)&inPropertyID)[1], -// ((char *)&inPropertyID)[0]); - } - } -} - -// -// handleAudioPackets:numberBytes:numberPackets:packetDescriptions: -// -// Object method which handles the implementation of MyPacketsProc -// -// Parameters: -// inInputData - the packet data -// inNumberBytes - byte size of the data -// inNumberPackets - number of packets in the data -// inPacketDescriptions - packet descriptions -// -- (void)handleAudioPackets:(const void *)inInputData - numberBytes:(UInt32)inNumberBytes - numberPackets:(UInt32)inNumberPackets - packetDescriptions:(AudioStreamPacketDescription *)inPacketDescriptions; -{ - @synchronized(self) - { - if ([self isFinishing]) - { - return; - } - - if (bitRate == 0) - { - // - // m4a and a few other formats refuse to parse the bitrate so - // we need to set an "unparseable" condition here. If you know - // the bitrate (parsed it another way) you can set it on the - // class if needed. - // - bitRate = ~0; - } - - // we have successfully read the first packests from the audio stream, so - // clear the "discontinuous" flag - if (discontinuous) - { - discontinuous = false; - } - - if (!audioQueue) - { - [self createQueue]; - } - } - - // the following code assumes we're streaming VBR data. for CBR data, the second branch is used. - if (inPacketDescriptions) - { - for (int i = 0; i < inNumberPackets; ++i) - { - SInt64 packetOffset = inPacketDescriptions[i].mStartOffset; - SInt64 packetSize = inPacketDescriptions[i].mDataByteSize; - size_t bufSpaceRemaining; - - if (processedPacketsCount < BitRateEstimationMaxPackets) - { - processedPacketsSizeTotal += packetSize; - processedPacketsCount += 1; - } - - @synchronized(self) - { - // If the audio was terminated before this point, then - // exit. - if ([self isFinishing]) - { - return; - } - - if (packetSize > packetBufferSize) - { - [self failWithErrorCode:AS_AUDIO_BUFFER_TOO_SMALL]; - } - - bufSpaceRemaining = packetBufferSize - bytesFilled; - } - - // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer. - if (bufSpaceRemaining < packetSize) - { - [self enqueueBuffer]; - } - - @synchronized(self) - { - // If the audio was terminated while waiting for a buffer, then - // exit. - if ([self isFinishing]) - { - return; - } - - // - // If there was some kind of issue with enqueueBuffer and we didn't - // make space for the new audio data then back out - // - if (bytesFilled + packetSize >= packetBufferSize) - { - return; - } - - // copy data to the audio queue buffer - AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; - memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)inInputData + packetOffset, packetSize); - - // fill out packet description - packetDescs[packetsFilled] = inPacketDescriptions[i]; - packetDescs[packetsFilled].mStartOffset = bytesFilled; - // keep track of bytes filled and packets filled - bytesFilled += packetSize; - packetsFilled += 1; - } - - // if that was the last free packet description, then enqueue the buffer. - size_t packetsDescsRemaining = kAQMaxPacketDescs - packetsFilled; - if (packetsDescsRemaining == 0) { - [self enqueueBuffer]; - } - } - } - else - { - size_t offset = 0; - while (inNumberBytes) - { - // if the space remaining in the buffer is not enough for this packet, then enqueue the buffer. - size_t bufSpaceRemaining = [self bufferSize] - bytesFilled; - if (bufSpaceRemaining < inNumberBytes) - { - [self enqueueBuffer]; - } - - @synchronized(self) - { - // If the audio was terminated while waiting for a buffer, then - // exit. - if ([self isFinishing]) - { - return; - } - - bufSpaceRemaining = [self bufferSize] - bytesFilled; - size_t copySize; - if (bufSpaceRemaining < inNumberBytes) - { - copySize = bufSpaceRemaining; - } - else - { - copySize = inNumberBytes; - } - - // - // If there was some kind of issue with enqueueBuffer and we didn't - // make space for the new audio data then back out - // - if (bytesFilled >= packetBufferSize) - { - return; - } - - // copy data to the audio queue buffer - AudioQueueBufferRef fillBuf = audioQueueBuffer[fillBufferIndex]; - memcpy((char*)fillBuf->mAudioData + bytesFilled, (const char*)(inInputData + offset), copySize); - - - // keep track of bytes filled and packets filled - bytesFilled += copySize; - packetsFilled = 0; - inNumberBytes -= copySize; - offset += copySize; - } - } - } -} - -// -// handleBufferCompleteForQueue:buffer: -// -// Handles the buffer completetion notification from the audio queue -// -// Parameters: -// inAQ - the queue -// inBuffer - the buffer -// -- (void)handleBufferCompleteForQueue:(AudioQueueRef)inAQ - buffer:(AudioQueueBufferRef)inBuffer -{ - unsigned int bufIndex = -1; - for (unsigned int i = 0; i < kNumAQBufs; ++i) - { - if (inBuffer == audioQueueBuffer[i]) - { - bufIndex = i; - break; - } - } - - if (bufIndex == -1) - { - [self failWithErrorCode:AS_AUDIO_QUEUE_BUFFER_MISMATCH]; - pthread_mutex_lock(&queueBuffersMutex); - pthread_cond_signal(&queueBufferReadyCondition); - pthread_mutex_unlock(&queueBuffersMutex); - return; - } - - // signal waiting thread that the buffer is free. - pthread_mutex_lock(&queueBuffersMutex); - inuse[bufIndex] = false; - buffersUsed--; - -// -// Enable this logging to measure how many buffers are queued at any time. -// -#if LOG_QUEUED_BUFFERS - NSLog(@"Queued buffers: %ld", buffersUsed); -#endif - - pthread_cond_signal(&queueBufferReadyCondition); - pthread_mutex_unlock(&queueBuffersMutex); -} - -// -// handlePropertyChangeForQueue:propertyID: -// -// Implementation for MyAudioQueueIsRunningCallback -// -// Parameters: -// inAQ - the audio queue -// inID - the property ID -// -- (void)handlePropertyChangeForQueue:(AudioQueueRef)inAQ - propertyID:(AudioQueuePropertyID)inID -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - - @synchronized(self) - { - if (inID == kAudioQueueProperty_IsRunning) - { - if (state == AS_STOPPING) - { - self.state = AS_STOPPED; - } - else if (state == AS_WAITING_FOR_QUEUE_TO_START) - { - // - // Note about this bug avoidance quirk: - // - // On cleanup of the AudioQueue thread, on rare occasions, there would - // be a crash in CFSetContainsValue as a CFRunLoopObserver was getting - // removed from the CFRunLoop. - // - // After lots of testing, it appeared that the audio thread was - // attempting to remove CFRunLoop observers from the CFRunLoop after the - // thread had already deallocated the run loop. - // - // By creating an NSRunLoop for the AudioQueue thread, it changes the - // thread destruction order and seems to avoid this crash bug -- or - // at least I haven't had it since (nasty hard to reproduce error!) - // - [NSRunLoop currentRunLoop]; - - self.state = AS_PLAYING; - } - else - { - NSLog(@"AudioQueue changed state in unexpected way."); - } - } - } - - [pool release]; -} - -#ifdef TARGET_OS_IPHONE -// -// handleInterruptionChangeForQueue:propertyID: -// -// Implementation for MyAudioQueueInterruptionListener -// -// Parameters: -// inAQ - the audio queue -// inID - the property ID -// -- (void)handleInterruptionChangeToState:(AudioQueuePropertyID)inInterruptionState -{ - if (inInterruptionState == kAudioSessionBeginInterruption) - { - [self pause]; - } - else if (inInterruptionState == kAudioSessionEndInterruption) - { - [self pause]; - } -} -#endif - -@end - -#endif diff --git a/iphone/Classes/MediaModule.h b/iphone/Classes/MediaModule.h index 026fad7d4b1..35077dddd99 100644 --- a/iphone/Classes/MediaModule.h +++ b/iphone/Classes/MediaModule.h @@ -10,6 +10,7 @@ #import "MediaPlayer/MediaPlayer.h" #import "TiMediaAudioSession.h" #import "TiMediaMusicPlayer.h" +#import "TiMediaTypes.h" #import "TiModule.h" #import "TiViewProxy.h" @@ -228,6 +229,16 @@ @property (nonatomic, readonly) NSString *AUDIO_SESSION_PORT_USBAUDIO; @property (nonatomic, readonly) NSString *AUDIO_SESSION_PORT_CARAUDIO; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_INITIALIZED; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_STARTING; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_WAITING_FOR_DATA; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_WAITING_FOR_QUEUE; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_PLAYING; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_BUFFERING; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_STOPPING; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_STOPPED; +@property (nonatomic, readonly) NSNumber *AUDIO_STATE_PAUSED; + @end #endif diff --git a/iphone/Classes/MediaModule.m b/iphone/Classes/MediaModule.m index d27d23c4e3f..71bb755d37d 100644 --- a/iphone/Classes/MediaModule.m +++ b/iphone/Classes/MediaModule.m @@ -16,7 +16,6 @@ #import "TiMediaAudioSession.h" #import "TiMediaItem.h" #import "TiMediaMusicPlayer.h" -#import "TiMediaTypes.h" #import "TiUtils.h" #import "TiViewProxy.h" @@ -244,7 +243,6 @@ - (NSString *)apiName MAKE_SYSTEM_UINT(AUDIO_SESSION_OVERRIDE_ROUTE_NONE, AVAudioSessionPortOverrideNone); MAKE_SYSTEM_UINT(AUDIO_SESSION_OVERRIDE_ROUTE_SPEAKER, AVAudioSessionPortOverrideSpeaker); - #endif // Constants for VideoPlayer.playbackState @@ -253,6 +251,17 @@ - (NSString *)apiName MAKE_SYSTEM_PROP(VIDEO_PLAYBACK_STATE_PLAYING, TiVideoPlayerPlaybackStatePlaying); MAKE_SYSTEM_PROP(VIDEO_PLAYBACK_STATE_STOPPED, TiVideoPlayerPlaybackStateStopped); +// Constants for AudioPlayer +MAKE_SYSTEM_PROP(AUDIO_STATE_INITIALIZED, TiAudioPlayerStateInitialized); +MAKE_SYSTEM_PROP(AUDIO_STATE_STARTING, TiAudioPlayerStateStartingFileThread); +MAKE_SYSTEM_PROP(AUDIO_STATE_WAITING_FOR_DATA, TiAudioPlayerStateWaitingForData); +MAKE_SYSTEM_PROP(AUDIO_STATE_WAITING_FOR_QUEUE, TiAudioPlayerStateWaitingForQueueToStart); +MAKE_SYSTEM_PROP(AUDIO_STATE_PLAYING, TiAudioPlayerStatePlaying); +MAKE_SYSTEM_PROP(AUDIO_STATE_BUFFERING, TiAudioPlayerStateBuffering); +MAKE_SYSTEM_PROP(AUDIO_STATE_STOPPING, TiAudioPlayerStateStopping); +MAKE_SYSTEM_PROP(AUDIO_STATE_STOPPED, TiAudioPlayerStateStopped); +MAKE_SYSTEM_PROP(AUDIO_STATE_PAUSED, TiAudioPlayerStatePaused); + //Constants for Camera #if defined(USE_TI_MEDIACAMERA_FRONT) || defined(USE_TI_MEDIACAMERA_REAR) || defined(USE_TI_MEDIACAMERA_FLASH_OFF) || defined(USE_TI_MEDIACAMERA_FLASH_AUTO) || defined(USE_TI_MEDIACAMERA_FLASH_ON) MAKE_SYSTEM_PROP(CAMERA_FRONT, UIImagePickerControllerCameraDeviceFront); diff --git a/iphone/Classes/ThirdpartyNS.h b/iphone/Classes/ThirdpartyNS.h index edbf70bcef6..059db94af7c 100644 --- a/iphone/Classes/ThirdpartyNS.h +++ b/iphone/Classes/ThirdpartyNS.h @@ -77,92 +77,6 @@ #define Reachability __TI_NS_SYMBOL(Reachability) #endif -// ASINetworkQueue -#ifndef ASINetworkQueue -#define ASINetworkQueue __TI_NS_SYMBOL(ASINetworkQueue) -#endif - -// ASIInputStream -#ifndef ASIInputStream -#define ASIInputStream __TI_NS_SYMBOL(ASIInputStream) -#endif - -// ASIHTTPRequest -#ifndef ASIHTTPRequest -#define ASIHTTPRequest __TI_NS_SYMBOL(ASIHTTPRequest) -#endif -#ifndef ASIHTTPRequestVersion -#define ASIHTTPRequestVersion __TI_NS_SYMBOL(ASIHTTPRequestVersion) -#endif -#ifndef NetworkRequestErrorDomain -#define NetworkRequestErrorDomain __TI_NS_SYMBOL(NetworkRequestErrorDomain) -#endif -#ifndef ASIWWANBandwidthThrottleAmount -#define ASIWWANBandwidthThrottleAmount __TI_NS_SYMBOL(ASIWWANBandwidthThrottleAmount) -#endif - -// ASIFormDataRequest -#ifndef ASIFormDataRequest -#define ASIFormDataRequest __TI_NS_SYMBOL(ASIFormDataRequest) -#endif - -// ASIAuthenticationDialog -#ifndef ASIAutorotatingViewController -#define ASIAutorotatingViewController __TI_NS_SYMBOL(ASIAutorotatingViewController) -#endif -#ifndef ASIAuthenticationDialog -#define ASIAuthenticationDialog __TI_NS_SYMBOL(ASIAuthenticationDialog) -#endif - -// ASIProgressDelegate -#ifndef ASIProgressDelegate -#define ASIProgressDelegate __TI_NS_SYMBOL(ASIProgressDelegate) -#endif - -// ASIHTTPRequestDelegate -#ifndef ASIHTTPRequestDelegate -#define ASIHTTPRequestDelegate __TI_NS_SYMBOL(ASIHTTPRequestDelegate) -#endif - -// ASIDownloadCache -#ifndef ASIDownloadCache -#define ASIDownloadCache __TI_NS_SYMBOL(ASIDownloadCache) -#endif - -// ASICacheDelegate -#ifndef ASICacheDelegate -#define ASICacheDelegate __TI_NS_SYMBOL(ASICacheDelegate) -#endif - -// ASIDataDecompressor -#ifndef ASIDataDecompressor -#define ASIDataDecompressor __TI_NS_SYMBOL(ASIDataDecompressor) -#endif - -// ASIDataCompressor -#ifndef ASIDataCompressor -#define ASIDataCompressor __TI_NS_SYMBOL(ASIDataCompressor) -#endif - -// AudioStreamerCUR -#ifndef AudioStreamerCUR -#define AudioStreamerCUR __TI_NS_SYMBOL(AudioStreamerCUR) -#endif - -// AudioStreamer -#ifndef ASStatusChangedNotification -#define ASStatusChangedNotification __TI_NS_SYMBOL(ASStatusChangedNotification) -#endif -#ifndef AudioStreamerDelegate -#define AudioStreamerDelegate __TI_NS_SYMBOL(AudioStreamerDelegate) -#endif -#ifndef AudioStreamerProtocol -#define AudioStreamerProtocol __TI_NS_SYMBOL(AudioStreamerProtocol) -#endif -#ifndef AudioStreamer -#define AudioStreamer __TI_NS_SYMBOL(AudioStreamer) -#endif - // SCListener #ifndef SCListener #define SCListener __TI_NS_SYMBOL(SCListener) diff --git a/iphone/Classes/TiMediaAudioPlayerProxy.h b/iphone/Classes/TiMediaAudioPlayerProxy.h index 1e8b0b68a01..1527abf43aa 100644 --- a/iphone/Classes/TiMediaAudioPlayerProxy.h +++ b/iphone/Classes/TiMediaAudioPlayerProxy.h @@ -6,43 +6,81 @@ */ #ifdef USE_TI_MEDIAAUDIOPLAYER -#import "AudioStreamer/AudioStreamer.h" +#import "TiMediaTypes.h" #import "TiProxy.h" -@interface TiMediaAudioPlayerProxy : TiProxy { +@class AVPlayer; + +@interface TiMediaAudioPlayerProxy : TiProxy { @private - NSURL *url; - UInt32 bufferSize; - double volume; - double duration; - AudioStreamer *player; - BOOL progress; - NSTimer *timer; + AVPlayer *_player; + NSURL *_url; + double _duration; + id _timeObserver; + TiAudioPlayerState _state; } -@property (nonatomic, readonly) NSURL *url; -@property (nonatomic, readwrite, assign) NSNumber *paused; -@property (nonatomic, readonly) NSNumber *playing; -@property (nonatomic, readonly) NSNumber *waiting; -@property (nonatomic, readonly) NSNumber *idle; -@property (nonatomic, readonly) NSNumber *bitRate; -@property (nonatomic, readonly) NSNumber *progress; -@property (nonatomic, readonly) NSNumber *state; -@property (nonatomic, readonly) NSNumber *duration; - -@property (nonatomic, copy) NSNumber *volume; - -@property (nonatomic, readwrite, assign) NSNumber *bufferSize; - -@property (nonatomic, readonly) NSNumber *STATE_INITIALIZED; -@property (nonatomic, readonly) NSNumber *STATE_STARTING; -@property (nonatomic, readonly) NSNumber *STATE_WAITING_FOR_DATA; -@property (nonatomic, readonly) NSNumber *STATE_WAITING_FOR_QUEUE; -@property (nonatomic, readonly) NSNumber *STATE_PLAYING; -@property (nonatomic, readonly) NSNumber *STATE_BUFFERING; -@property (nonatomic, readonly) NSNumber *STATE_STOPPING; -@property (nonatomic, readonly) NSNumber *STATE_STOPPED; -@property (nonatomic, readonly) NSNumber *STATE_PAUSED; +- (void)setPaused:(NSNumber *)paused __deprecated_msg("Deprecated in favor of pause()"); + +- (void)play:(id)unused __deprecated_msg("Deprecated in favor of start()"); + +- (NSNumber *)waiting; + +- (NSNumber *)idle; + +- (NSNumber *)playing; + +- (NSNumber *)paused; + +- (NSNumber *)buffering; + +- (NSNumber *)bitRate; + +- (NSNumber *)progress; + +- (NSNumber *)state; + +- (NSNumber *)duration; + +- (NSNumber *)volume; + +- (void)setVolume:(NSNumber *)volume; + +- (void)setBufferSize:(NSNumber *)bufferSize; + +- (void)setAllowsExternalPlayback:(NSNumber *)allowsExternalPlayback; + +- (NSNumber *)allowsExternalPlayback; + +- (void)setRate:(NSNumber *)rate; + +- (NSNumber *)rate; + +- (void)setMuted:(NSNumber *)muted; + +- (NSNumber *)muted; + +- (void)externalPlaybackActive; + +- (NSNumber *)bufferSize; + +- (void)setUrl:(id)url; + +- (NSString *)url; + +- (void)seekToTime:(id)time; + +- (void)start:(id)unused; + +- (void)restart:(id)args; + +- (void)stop:(id)unused; + +- (void)pause:(id)unused; + +- (void)release:(id)unused; + +- (NSString *)stateDescription:(id)state; @end diff --git a/iphone/Classes/TiMediaAudioPlayerProxy.m b/iphone/Classes/TiMediaAudioPlayerProxy.m index b1662d8e097..369b7cf52fb 100644 --- a/iphone/Classes/TiMediaAudioPlayerProxy.m +++ b/iphone/Classes/TiMediaAudioPlayerProxy.m @@ -1,15 +1,17 @@ /** * Appcelerator Titanium Mobile - * Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved. + * Copyright (c) 2009-2018 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Apache Public License * Please see the LICENSE included with this distribution for details. */ + #ifdef USE_TI_MEDIAAUDIOPLAYER +#import + #import "TiMediaAudioPlayerProxy.h" #import "TiMediaAudioSession.h" #import "TiUtils.h" -#include @implementation TiMediaAudioPlayerProxy @@ -17,25 +19,19 @@ @implementation TiMediaAudioPlayerProxy - (void)_initWithProperties:(NSDictionary *)properties { - volume = [TiUtils doubleValue:@"volume" properties:properties def:1.0]; - url = [[TiUtils toURL:[properties objectForKey:@"url"] proxy:self] retain]; + [super _initWithProperties:properties]; + _url = [TiUtils toURL:[properties objectForKey:@"url"] proxy:self]; } - (void)_destroy { - if (timer != nil) { - [timer invalidate]; - RELEASE_TO_NIL(timer); + if (_state == TiAudioPlayerStatePlaying || _state == TiAudioPlayerStatePaused) { + [self stop:nil]; } - if (player != nil) { - if ([player isPlaying] || [player isPaused] || [player isWaiting]) { - [player stop]; - [[TiMediaAudioSession sharedSession] stopAudioSession]; - } - } - [player setDelegate:nil]; - RELEASE_TO_NIL(player); - RELEASE_TO_NIL(timer); + + [self removeNotificationObserver]; + + _player = nil; [super _destroy]; } @@ -47,300 +43,514 @@ - (NSString *)apiName - (void)_listenerAdded:(NSString *)type count:(int)count { if (count == 1 && [type isEqualToString:@"progress"]) { - progress = YES; + __weak TiMediaAudioPlayerProxy *weakSelf = self; + _timeObserver = [[self player] addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1.0, NSEC_PER_SEC) + queue:nil + usingBlock:^(CMTime time) { + TiMediaAudioPlayerProxy *strongSelf = weakSelf; + [strongSelf fireEvent:@"progress" + withObject:@{ + @"progress" : NUMINT(CMTimeGetSeconds(time) * 1000) + }]; + }]; } } - (void)_listenerRemoved:(NSString *)type count:(int)count { if (count == 0 && [type isEqualToString:@"progress"]) { - progress = NO; + [[self player] removeTimeObserver:_timeObserver]; + _timeObserver = nil; } } -- (AudioStreamer *)player +- (AVPlayer *)player { - if (player == nil) { - if (url == nil) { - [self throwException:@"invalid url" subreason:@"url has not been set" location:CODELOCATION]; - } - player = [[AudioStreamer alloc] initWithURL:url]; - [player setDelegate:self]; - [player setBufferSize:bufferSize]; - [player setVolume:volume]; - - if (progress) { - // create progress callback timer that fires once per second. we might want to eventually make this - // more configurable but for now that's sufficient for most apps that want to display progress updates on the stream - timer = [[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateProgress:) userInfo:nil repeats:YES] retain]; + if (_player == nil) { + if (_url == nil) { + [self throwException:NSLocalizedString(@"Invalid URL passed to the audio-player", nil) + subreason:NSLocalizedString(@"The \"url\" probably has not been set to a valid value.", nil) + location:CODELOCATION]; } + _player = [AVPlayer playerWithURL:_url]; + [self addNotificationObserver]; + _state = TiAudioPlayerStateInitialized; } - return player; + return _player; } -- (void)destroyPlayer -{ - if (timer != nil) { - [timer invalidate]; - RELEASE_TO_NIL(timer); - } - if (player != nil) { - if ([player isPlaying] || [player isPaused] || [player isWaiting]) { - [player stop]; - [[TiMediaAudioSession sharedSession] stopAudioSession]; - } - [player setDelegate:nil]; - RELEASE_TO_NIL(player); - } -} +#pragma mark Deprecated APIs -- (void)restart:(id)args +- (void)setPaused:(NSNumber *)paused { - BOOL playing = [player isPlaying] || [player isPaused] || [player isWaiting]; - [self destroyPlayer]; + DEPRECATED_REPLACED(@"Media.AudioPlayer.setPaused", @"7.3.0", @"Media.AudioPlayer.pause"); - if (playing) { - [[self player] start]; + if ([TiUtils boolValue:paused]) { + [[self player] pause]; } else { - // just create it - [self player]; + [[self player] play]; } } +- (void)play:(id)unused +{ + DEPRECATED_REPLACED(@"Media.AudioPlayer.play", @"7.3.0", @"Media.AudioPlayer.start"); + [self start:unused]; +} + #pragma mark Public APIs -- (void)setPaused:(NSNumber *)paused +- (NSNumber *)waiting { - if (player != nil) { - if ([TiUtils boolValue:paused]) { - [player pause]; - } else { - [player start]; - } - } + return NUMBOOL(_state == TiAudioPlayerStateWaitingForQueueToStart || _state == TiAudioPlayerStateBuffering); } -#define PLAYER_PROP_BOOL(name, func) \ - -(NSNumber *)name \ - { \ - if (player == nil) { \ - return NUMBOOL(NO); \ - } \ - return NUMBOOL([player func]); \ - } +- (NSNumber *)idle +{ + return NUMBOOL(_state == TiAudioPlayerStateInitialized); +} -#define PLAYER_PROP_DOUBLE(name, func) \ - -(NSNumber *)name \ - { \ - if (player == nil) { \ - return NUMDOUBLE(0); \ - } \ - return NUMDOUBLE([player func]); \ - } +- (NSNumber *)playing +{ + return NUMBOOL(_state == TiAudioPlayerStatePlaying); +} -PLAYER_PROP_BOOL(waiting, isWaiting); -PLAYER_PROP_BOOL(idle, isIdle); -PLAYER_PROP_BOOL(playing, isPlaying); -PLAYER_PROP_BOOL(paused, isPaused); +- (NSNumber *)buffering +{ + return NUMBOOL(_state == TiAudioPlayerStateBuffering); +} -PLAYER_PROP_DOUBLE(bitRate, bitRate); -PLAYER_PROP_DOUBLE(progress, progress); -PLAYER_PROP_DOUBLE(state, state); +- (NSNumber *)bitRate +{ + return NUMFLOAT([[self player] rate]); +} + +- (NSNumber *)progress +{ + return NUMDOUBLE(CMTimeGetSeconds([[self player] currentTime]) * 1000); +} + +- (NSNumber *)state +{ + return NUMDOUBLE(_state); +} - (NSNumber *)duration { - if (player != nil) { - //Convert duration to milliseconds (parity with progress/Android) - duration = (int)([player duration] * 1000); + if (CMTimeGetSeconds([[[self player] currentItem] duration]) == CMTimeGetSeconds(kCMTimeIndefinite)) { + _duration = 0.0; + } else { + // Convert duration to milliseconds (parity with progress/Android) + _duration = (int)(CMTimeGetSeconds([[[self player] currentItem] duration]) * 1000); } - return NUMDOUBLE(duration); +} + +- (NSNumber *)paused +{ + return NUMBOOL(_state == TiAudioPlayerStatePaused); } - (NSNumber *)volume { - if (player != nil) { - volume = [player volume]; - } - return NUMDOUBLE(volume); + return NUMFLOAT([[self player] volume]); } -- (void)setVolume:(NSNumber *)newVolume +- (void)setVolume:(NSNumber *)volume { - volume = [TiUtils doubleValue:newVolume def:volume]; - if (player != nil) { - [player setVolume:volume]; - } + [[self player] setVolume:[TiUtils floatValue:volume def:1.0]]; } -- (void)setBufferSize:(NSNumber *)bufferSize_ +- (void)setBufferSize:(NSNumber *)bufferSize { - bufferSize = [bufferSize_ unsignedIntValue]; - if (player != nil) { - [player setBufferSize:bufferSize]; - } + [[[self player] currentItem] setPreferredForwardBufferDuration:[bufferSize doubleValue] * 1000]; +} + +- (void)setAllowsExternalPlayback:(NSNumber *)allowsExternalPlayback +{ + [[self player] setAllowsExternalPlayback:[TiUtils boolValue:allowsExternalPlayback]]; +} + +- (NSNumber *)allowsExternalPlayback +{ + return NUMBOOL([[self player] allowsExternalPlayback]); +} + +- (void)setRate:(NSNumber *)rate +{ + [[self player] setRate:[TiUtils floatValue:rate]]; +} + +- (NSNumber *)rate +{ + return NUMFLOAT([[self player] rate]); +} + +- (void)setMuted:(NSNumber *)muted +{ + [[self player] setMuted:[TiUtils boolValue:muted]]; +} + +- (NSNumber *)muted +{ + return NUMBOOL([[self player] isMuted]); +} + +- (void)externalPlaybackActive +{ + return NUMBOOL([[self player] isExternalPlaybackActive]); } - (NSNumber *)bufferSize { - return [NSNumber numberWithUnsignedInteger:((bufferSize) ? bufferSize : kAQDefaultBufSize)]; + return NUMDOUBLE([[[self player] currentItem] preferredForwardBufferDuration]); } -- (void)setUrl:(id)args +- (void)setUrl:(id)url { if (![NSThread isMainThread]) { TiThreadPerformOnMainThread(^{ - [self setUrl:args]; + [self setUrl:url]; }, YES); return; } - RELEASE_TO_NIL(url); - ENSURE_SINGLE_ARG(args, NSString); - url = [[TiUtils toURL:args proxy:self] retain]; - if (player != nil) { + + ENSURE_SINGLE_ARG(url, NSString); + _url = [TiUtils toURL:url proxy:self]; + + // Properly clean up old observer before changing player item + if (_player != nil) { + // Remove old KVO-observer + [self removeNotificationObserver]; + + // Change player item + [[self player] replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:_url]]; + + // Add new KVO-observer + [self addNotificationObserver]; + + // Restart (stop -> start) player [self restart:nil]; } } -- (NSURL *)url +- (NSString *)url { - return url; + return [_url absoluteString]; } -- (void)play:(id)args +- (void)seekToTime:(id)time { - [self start:args]; + ENSURE_SINGLE_ARG(time, NSNumber); + + if (_player == nil) { + return; + } + + float formattedTime = [TiUtils floatValue:time] / 1000; + __weak TiMediaAudioPlayerProxy *weakSelf = self; + + [_player seekToTime:CMTimeMake(formattedTime, 1) + completionHandler:^(BOOL finished) { + TiMediaAudioPlayerProxy *strongSelf = weakSelf; + + if ([strongSelf _hasListeners:@"seek"]) { + [strongSelf fireEvent:@"seek" withObject:@{ @"finished" : NUMBOOL(finished) }]; + } + }]; } -// Only need to ensure the UI thread when starting; and we should actually wait until it's finished so -// that execution flow is correct (if we stop/pause immediately after) -- (void)start:(id)args +- (void)start:(id)unused { if (![NSThread isMainThread]) { TiThreadPerformOnMainThread(^{ - [self start:args]; + [self start:unused]; }, YES); return; } + + _state = TiAudioPlayerStateStartingFileThread; + // indicate we're going to start playing if (![[TiMediaAudioSession sharedSession] canPlayback]) { + _state = TiAudioPlayerStateStopped; [self throwException:@"Improper audio session mode for playback" subreason:[[TiMediaAudioSession sharedSession] sessionMode] location:CODELOCATION]; } - if (player == nil || !([player isPlaying] || [player isPaused] || [player isWaiting])) { + if (_player == nil || !(_state == TiAudioPlayerStatePlaying || _state == TiAudioPlayerStatePaused)) { [[TiMediaAudioSession sharedSession] startAudioSession]; } - [[self player] start]; + + [[self player] play]; +} + +- (void)restart:(id)args +{ + [self stop:nil]; + [self start:nil]; } -- (void)stop:(id)args +- (void)stop:(id)unused { if (![NSThread isMainThread]) { TiThreadPerformOnMainThread(^{ - [self stop:args]; + [self stop:unused]; }, YES); return; } - if (player != nil) { - if ([player isPlaying] || [player isPaused] || [player isWaiting]) { - [player stop]; - [[TiMediaAudioSession sharedSession] stopAudioSession]; - } - } + + _state = TiAudioPlayerStateStopping; + + [[self player] pause]; + [[self player] seekToTime:kCMTimeZero]; + [[TiMediaAudioSession sharedSession] stopAudioSession]; } -- (void)pause:(id)args +- (void)pause:(id)unused { if (![NSThread isMainThread]) { TiThreadPerformOnMainThread(^{ - [self pause:args]; + [self pause:unused]; }, YES); return; } - if (player != nil) { - [player pause]; - } + + [[self player] pause]; +} + +- (void)release:(id)unused +{ + [self stop:nil]; + _player = nil; +} + +- (NSString *)stateDescription:(id)state +{ + ENSURE_SINGLE_ARG(state, NSNumber); + return [TiMediaAudioPlayerProxy _stateToString:[TiUtils intValue:state]]; } -MAKE_SYSTEM_PROP(STATE_INITIALIZED, AS_INITIALIZED); -MAKE_SYSTEM_PROP(STATE_STARTING, AS_STARTING_FILE_THREAD); -MAKE_SYSTEM_PROP(STATE_WAITING_FOR_DATA, AS_WAITING_FOR_DATA); -MAKE_SYSTEM_PROP(STATE_WAITING_FOR_QUEUE, AS_WAITING_FOR_QUEUE_TO_START); -MAKE_SYSTEM_PROP(STATE_PLAYING, AS_PLAYING); -MAKE_SYSTEM_PROP(STATE_BUFFERING, AS_BUFFERING); -MAKE_SYSTEM_PROP(STATE_STOPPING, AS_STOPPING); -MAKE_SYSTEM_PROP(STATE_STOPPED, AS_STOPPED); -MAKE_SYSTEM_PROP(STATE_PAUSED, AS_PAUSED); +#pragma mark Utilities -- (NSString *)stateToString:(int)state ++ (NSString *)_stateToString:(NSInteger)state { switch (state) { - case AS_INITIALIZED: - return @"initialized"; - case AS_STARTING_FILE_THREAD: - return @"starting"; - case AS_WAITING_FOR_DATA: - return @"waiting_for_data"; - case AS_WAITING_FOR_QUEUE_TO_START: - return @"waiting_for_queue"; - case AS_PLAYING: - return @"playing"; - case AS_BUFFERING: - return @"buffering"; - case AS_STOPPING: - return @"stopping"; - case AS_STOPPED: - return @"stopped"; - case AS_PAUSED: - return @"paused"; + case TiAudioPlayerStateInitialized: + return NSLocalizedString(@"initialized", nil); + case TiAudioPlayerStateStartingFileThread: + return NSLocalizedString(@"starting", nil); + case TiAudioPlayerStateWaitingForData: + return NSLocalizedString(@"waiting_for_data", nil); + case TiAudioPlayerStateWaitingForQueueToStart: + return NSLocalizedString(@"waiting_for_queue", nil); + case TiAudioPlayerStatePlaying: + return NSLocalizedString(@"playing", nil); + case TiAudioPlayerStateBuffering: + return NSLocalizedString(@"buffering", nil); + case TiAudioPlayerStateStopping: + return NSLocalizedString(@"stopping", nil); + case TiAudioPlayerStateStopped: + return NSLocalizedString(@"stopped", nil); + case TiAudioPlayerStatePaused: + return NSLocalizedString(@"paused", nil); } - return @"unknown"; + return NSLocalizedString(@"unknown", nil); } -- (NSString *)stateDescription:(id)arg +#pragma mark Observer + +- (void)addNotificationObserver { - ENSURE_SINGLE_ARG(arg, NSNumber); - return [self stateToString:[TiUtils intValue:arg]]; + WARN_IF_BACKGROUND_THREAD; //NSNotificationCenter is not threadsafe! + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + // The AVPlayer does not properly support state management on iOS < 10. + // Remove this once we bump the minimum iOS version to 10+. + if ([TiUtils isIOS10OrGreater]) { + // iOS 10+: For playbackState property / playbackstate event + [[self player] addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; + } else { + // iOS < 10: For playbackstate event + [[self player] addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; + } + + // For "error" event + [nc addObserver:self selector:@selector(handlePlayerErrorNotification:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:_player.currentItem]; + + // For "complete" event + [nc addObserver:self selector:@selector(handlePlayerCompleteNotification:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem]; + + // Buffering + [[[self player] currentItem] addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; + [[[self player] currentItem] addObserver:self forKeyPath:@"playbackBufferFull" options:NSKeyValueObservingOptionNew context:nil]; + + // Timed metadata + [[[self player] currentItem] addObserver:self forKeyPath:@"timedMetadata" options:NSKeyValueObservingOptionNew context:nil]; } -#pragma mark Delegates +- (void)removeNotificationObserver +{ + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + + [nc removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:nil]; + [nc removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil]; -- (void)playbackStateChanged:(id)sender + if (_player == nil) { + return; + } + + if ([TiUtils isIOS10OrGreater]) { + [[self player] removeObserver:self forKeyPath:@"timeControlStatus"]; + } else { + [[self player] removeObserver:self forKeyPath:@"rate"]; + } + + [[[self player] currentItem] removeObserver:self forKeyPath:@"playbackBufferEmpty"]; + [[[self player] currentItem] removeObserver:self forKeyPath:@"playbackBufferFull"]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([self _hasListeners:@"change"]) { - NSDictionary *event = [NSDictionary dictionaryWithObjectsAndKeys:[self state], @"state", [self stateToString:player.state], @"description", nil]; - [self fireEvent:@"change" withObject:event]; + if ([TiUtils isIOS10OrGreater]) { + if (object == _player && [keyPath isEqualToString:@"timeControlStatus"]) { + [self handleTimeControlStatusNotification:nil]; + } + } else { + if (object == _player && [keyPath isEqualToString:@"rate"]) { + [self handlePlaybackStateChangeNotification:nil]; + } + } + + if (object == _player.currentItem && [keyPath isEqualToString:@"playbackBufferEmpty"]) { + _state = TiAudioPlayerStateBuffering; + } + + if (object == _player.currentItem && [keyPath isEqualToString:@"playbackBufferFull"]) { + _state = TiAudioPlayerStateWaitingForQueueToStart; } - if (player.errorCode != AS_NO_ERROR && player.state == AS_STOPPED) { - [[TiMediaAudioSession sharedSession] stopAudioSession]; + + if (object == _player.currentItem && [keyPath isEqualToString:@"timedMetadata"]) { + [self handleTimedMetadataNotification:_player.currentItem]; } } -- (void)updateProgress:(NSTimer *)updatedTimer +// iOS < 10 +- (void)handlePlaybackStateChangeNotification:(NSNotification *)note { - if (player != nil && [player isPlaying]) { - double value = 0; + TiAudioPlayerState oldState = _state; + + switch (_player.status) { + case AVPlayerStatusUnknown: + case AVPlayerStatusFailed: + _state = TiAudioPlayerStateStopped; + break; + case AVPlayerStatusReadyToPlay: + if (_player.rate == 1.0) { + _state = TiAudioPlayerStatePlaying; + } else if (_player.currentItem.currentTime.value == 0 || oldState == TiAudioPlayerStateStopping) { + _state = TiAudioPlayerStateStopped; + } else { + _state = TiAudioPlayerStatePaused; + } + break; + } - if (player.bitRate != 0.0) { - value = player.progress; + if ([self _hasListeners:@"change"] && oldState != _state) { + [self fireEvent:@"change" + withObject:@{ + @"state" : NUMINTEGER(_state), + @"description" : [TiMediaAudioPlayerProxy _stateToString:_state] + }]; + } +} + +// iOS 10+ +- (void)handleTimeControlStatusNotification:(NSNotification *)note +{ + TiAudioPlayerState oldState = _state; + + if (_player.timeControlStatus == AVPlayerTimeControlStatusPlaying) { + _state = TiAudioPlayerStatePlaying; + } else if (_player.timeControlStatus == AVPlayerTimeControlStatusPaused) { + if (_player.currentItem.currentTime.value == 0.0 || oldState == TiAudioPlayerStateStopping) { + _state = TiAudioPlayerStateStopped; + } else { + _state = TiAudioPlayerStatePaused; } - NSDictionary *event = [NSDictionary dictionaryWithObject:NUMDOUBLE(value) forKey:@"progress"]; - [self fireEvent:@"progress" withObject:event]; + } else if (_player.timeControlStatus == AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate) { + _state = TiAudioPlayerStateWaitingForQueueToStart; + } + + if ([self _hasListeners:@"change"] && oldState != _state) { + [self fireEvent:@"change" + withObject:@{ + @"state" : NUMINTEGER(_state), + @"description" : [TiMediaAudioPlayerProxy _stateToString:_state] + }]; } } -- (void)errorReceived:(id)sender +#pragma mark Events + +- (void)handlePlayerErrorNotification:(NSNotification *)note { + NSError *error = note.userInfo[AVPlayerItemFailedToPlayToEndTimeErrorKey]; + _state = TiAudioPlayerStateStopped; + if ([self _hasListeners:@"error"]) { - NSDictionary *event = [TiUtils dictionaryWithCode:player.errorCode message:[AudioStreamer stringForErrorCode:player.errorCode]]; - [self fireEvent:@"error" withObject:event]; + [self fireEvent:@"error" withObject:@{ @"error" : error.localizedDescription }]; + } +} + +- (void)handleTimedMetadataNotification:(AVPlayerItem *)playerItem +{ + if (![self _hasListeners:@"metadata"]) { + return; + } + + NSMutableArray *result = [NSMutableArray arrayWithCapacity:playerItem.timedMetadata.count]; + + for (AVMetadataItem *metadata in playerItem.timedMetadata) { + [result addObject:@{ + @"key" : metadata.key, + @"keySpace" : metadata.keySpace, + @"value" : metadata.value, + @"extraAttributes" : metadata.extraAttributes + }]; } + + [self fireEvent:@"metadata" withObject:@{ @"items" : result }]; } + +- (void)handlePlayerCompleteNotification:(NSNotification *)note +{ + if ([self _hasListeners:@"complete"]) { + NSMutableDictionary *event = [NSMutableDictionary dictionaryWithObjectsAndKeys:NUMBOOL(_player.error == nil), @"success", nil]; + if (_player.error != nil) { + [event setObject:_player.error.localizedDescription forKey:@"error"]; + [event setObject:NUMINTEGER(_player.error.code) forKey:@"code"]; + } + [self fireEvent:@"complete" withObject:event]; + } +} + +#pragma mark Constants + +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_INITIALIZED, TiAudioPlayerStateInitialized, @"Media.AudioPlayer.STATE_INITIALIZED", @"7.3.0", @"Media.AUDIO_STATE_INITIALIZED"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_STARTING, TiAudioPlayerStateStartingFileThread, @"Media.AudioPlayer.STATE_STARTING", @"7.3.0", @"Media.STATE_STARTING"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_WAITING_FOR_DATA, TiAudioPlayerStateWaitingForData, @"Media.AudioPlayer.STATE_WAITING_FOR_DATA", @"7.3.0", @"Media.AUDIO_STATE_WAITING_FOR_DATA"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_WAITING_FOR_QUEUE, TiAudioPlayerStateWaitingForQueueToStart, @"Media.AudioPlayer.STATE_WAITING_FOR_QUEUE", @"7.3.0", @"Media.AUDIO_STATE_WAITING_FOR_QUEUE"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_PLAYING, TiAudioPlayerStatePlaying, @"Media.AudioPlayer.STATE_PLAYING", @"7.3.0", @"Media.AUDIO_STATE_PLAYING"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_BUFFERING, TiAudioPlayerStateBuffering, @"Media.AudioPlayer.STATE_BUFFERING", @"7.3.0", @"Media.AUDIO_STATE_BUFFERING"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_STOPPING, TiAudioPlayerStateStopping, @"Media.AudioPlayer.STATE_STOPPING", @"7.3.0", @"Media.AUDIO_STATE_STOPPING"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_STOPPED, TiAudioPlayerStateStopped, @"Media.AudioPlayer.STATE_STOPPED", @"7.3.0", @"Media.AUDIO_STATE_STOPPED"); +MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(STATE_PAUSED, TiAudioPlayerStatePaused, @"Media.AudioPlayer.STATE_PAUSED", @"7.3.0", @"Media.AUDIO_STATE_PAUSED"); + @end #endif diff --git a/iphone/Classes/TiMediaTypes.h b/iphone/Classes/TiMediaTypes.h index 3dbfa6b394f..0821ec29ed8 100644 --- a/iphone/Classes/TiMediaTypes.h +++ b/iphone/Classes/TiMediaTypes.h @@ -38,4 +38,19 @@ typedef enum { RecordPaused = 2 } RecorderState; +#pragma mark AudioPlayer + +typedef NS_ENUM(NSInteger, TiAudioPlayerState) { + TiAudioPlayerStateBuffering = 0, + TiAudioPlayerStateInitialized, + TiAudioPlayerStatePaused, + TiAudioPlayerStatePlaying, + TiAudioPlayerStateStartingFileThread, + TiAudioPlayerStateStopped, + TiAudioPlayerStateStopping, + TiAudioPlayerStateWaitingForData, // Unused + TiAudioPlayerStateWaitingForQueueToStart, + TiAudioPlayerStateFlushingEOF, // Unused +}; + #endif /* TiMediaTypes_h */ diff --git a/iphone/iphone/Titanium.xcodeproj/project.pbxproj b/iphone/iphone/Titanium.xcodeproj/project.pbxproj index b0e08fea013..ead1fe984a3 100644 --- a/iphone/iphone/Titanium.xcodeproj/project.pbxproj +++ b/iphone/iphone/Titanium.xcodeproj/project.pbxproj @@ -65,7 +65,6 @@ 24CA895B111161050084E2DE /* AFItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA890B111161050084E2DE /* AFItemView.m */; }; 24CA895C111161050084E2DE /* AFOpenFlowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA890E111161050084E2DE /* AFOpenFlowView.m */; }; 24CA895D111161050084E2DE /* AFUIImageReflection.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8910111161050084E2DE /* AFUIImageReflection.m */; }; - 24CA8968111161050084E2DE /* AudioStreamer.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8929111161050084E2DE /* AudioStreamer.m */; }; 24CA897D111161050084E2DE /* PlausibleDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA894F111161050084E2DE /* PlausibleDatabase.m */; }; 24CA897E111161050084E2DE /* PLSqliteDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8954111161050084E2DE /* PLSqliteDatabase.m */; }; 24CA897F111161050084E2DE /* PLSqlitePreparedStatement.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8956111161050084E2DE /* PLSqlitePreparedStatement.m */; }; @@ -128,7 +127,7 @@ 24CA8BBB111161FE0084E2DE /* TiModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8AB8111161FD0084E2DE /* TiModule.m */; }; 24CA8BBC111161FE0084E2DE /* TiMediaVideoPlayerProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8ABA111161FD0084E2DE /* TiMediaVideoPlayerProxy.m */; }; 24CA8BBD111161FE0084E2DE /* TiMediaSoundProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8ABC111161FD0084E2DE /* TiMediaSoundProxy.m */; }; - 24CA8BBE111161FE0084E2DE /* TiMediaAudioPlayerProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8ABE111161FD0084E2DE /* TiMediaAudioPlayerProxy.m */; }; + 24CA8BBE111161FE0084E2DE /* TiMediaAudioPlayerProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8ABE111161FD0084E2DE /* TiMediaAudioPlayerProxy.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; 24CA8BC2111161FE0084E2DE /* TiHost.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8AC6111161FD0084E2DE /* TiHost.m */; }; 24CA8BC3111161FE0084E2DE /* TiFilesystemFileProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8AC8111161FD0084E2DE /* TiFilesystemFileProxy.m */; }; 24CA8BC7111161FE0084E2DE /* TiDimension.m in Sources */ = {isa = PBXBuildFile; fileRef = 24CA8AD1111161FD0084E2DE /* TiDimension.m */; }; @@ -310,7 +309,6 @@ DA4E12D711C0A55F00A55BAB /* TiContactsGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4E12D611C0A55F00A55BAB /* TiContactsGroup.m */; }; DA864E3211A2314A00B9CD68 /* TiMediaMusicPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = DA864E3111A2314A00B9CD68 /* TiMediaMusicPlayer.m */; }; DA9BE36F127F8F6C000FE9E1 /* UIImageExtras.m in Sources */ = {isa = PBXBuildFile; fileRef = DA9BE36E127F8F6C000FE9E1 /* UIImageExtras.m */; }; - DA9FF584128A1F3600457759 /* AudioStreamerCUR.m in Sources */ = {isa = PBXBuildFile; fileRef = DA9FF582128A1F3600457759 /* AudioStreamerCUR.m */; }; DAA4B2B4134E75AC00AB6011 /* CodecModule.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA4B2B3134E75AC00AB6011 /* CodecModule.m */; }; DAA72343156D642400757987 /* TiConsole.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA72342156D642400757987 /* TiConsole.m */; }; DAA905CE11A359F10030B119 /* TiMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = DAA905CD11A359F10030B119 /* TiMediaItem.m */; }; @@ -438,8 +436,6 @@ 24CA890E111161050084E2DE /* AFOpenFlowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFOpenFlowView.m; sourceTree = ""; }; 24CA890F111161050084E2DE /* AFUIImageReflection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFUIImageReflection.h; sourceTree = ""; }; 24CA8910111161050084E2DE /* AFUIImageReflection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFUIImageReflection.m; sourceTree = ""; }; - 24CA8928111161050084E2DE /* AudioStreamer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioStreamer.h; sourceTree = ""; }; - 24CA8929111161050084E2DE /* AudioStreamer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioStreamer.m; sourceTree = ""; }; 24CA894E111161050084E2DE /* PlausibleDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlausibleDatabase.h; sourceTree = ""; }; 24CA894F111161050084E2DE /* PlausibleDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlausibleDatabase.m; sourceTree = ""; }; 24CA8950111161050084E2DE /* PLDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PLDatabase.h; sourceTree = ""; }; @@ -948,8 +944,6 @@ DA864E3111A2314A00B9CD68 /* TiMediaMusicPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiMediaMusicPlayer.m; sourceTree = ""; }; DA9BE36D127F8F6C000FE9E1 /* UIImageExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIImageExtras.h; sourceTree = ""; }; DA9BE36E127F8F6C000FE9E1 /* UIImageExtras.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIImageExtras.m; sourceTree = ""; }; - DA9FF581128A1F3600457759 /* AudioStreamerCUR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioStreamerCUR.h; sourceTree = ""; }; - DA9FF582128A1F3600457759 /* AudioStreamerCUR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioStreamerCUR.m; sourceTree = ""; }; DAA4B2B2134E75AC00AB6011 /* CodecModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodecModule.h; sourceTree = ""; }; DAA4B2B3134E75AC00AB6011 /* CodecModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodecModule.m; sourceTree = ""; }; DAA72341156D642400757987 /* TiConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiConsole.h; sourceTree = ""; }; @@ -1139,7 +1133,6 @@ 24CA8909111161050084E2DE /* AFOpenFlow */, 24CA89A6111161230084E2DE /* Misc */, 24CA89A7111161460084E2DE /* Kroll */, - 24CA8927111161050084E2DE /* AudioStreamer */, 24CA894D111161050084E2DE /* PlausibleDatabase */, 50115A9315D5DE0500122055 /* ThirdpartyNS.h */, ); @@ -1163,18 +1156,6 @@ path = ../Classes/AFOpenFlow; sourceTree = SOURCE_ROOT; }; - 24CA8927111161050084E2DE /* AudioStreamer */ = { - isa = PBXGroup; - children = ( - DA9FF581128A1F3600457759 /* AudioStreamerCUR.h */, - DA9FF582128A1F3600457759 /* AudioStreamerCUR.m */, - 24CA8928111161050084E2DE /* AudioStreamer.h */, - 24CA8929111161050084E2DE /* AudioStreamer.m */, - ); - name = AudioStreamer; - path = ../Classes/AudioStreamer; - sourceTree = SOURCE_ROOT; - }; 24CA894D111161050084E2DE /* PlausibleDatabase */ = { isa = PBXGroup; children = ( @@ -2018,6 +1999,7 @@ CEFFA0BB1A8A80810078F310 /* Extensions */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, + DB30D3FB207384600070E895 /* Recovered References */, ); name = CustomTemplate; sourceTree = ""; @@ -2426,6 +2408,13 @@ name = Toolbar; sourceTree = ""; }; + DB30D3FB207384600070E895 /* Recovered References */ = { + isa = PBXGroup; + children = ( + ); + name = "Recovered References"; + sourceTree = ""; + }; DBCFCB791F8261AB00A4CE61 /* SBJSON */ = { isa = PBXGroup; children = ( @@ -2544,7 +2533,6 @@ 84A00FF517FB675E00D4BF94 /* TiSnapBehavior.m in Sources */, 243ECCBE112698AA00639DF4 /* TiUITextWidgetProxy.m in Sources */, 243ECCAA1126981C00639DF4 /* TiUITableViewSectionProxy.m in Sources */, - 24CA8968111161050084E2DE /* AudioStreamer.m in Sources */, 243ECCB91126989700639DF4 /* TiUITextWidget.m in Sources */, 24CA897D111161050084E2DE /* PlausibleDatabase.m in Sources */, 24CA897E111161050084E2DE /* PLSqliteDatabase.m in Sources */, @@ -2758,7 +2746,6 @@ B473FBA4126FB2DF00E29C73 /* TiPublicAPI.m in Sources */, B480501F1278AB010030AA3F /* TiThreading.m in Sources */, DA9BE36F127F8F6C000FE9E1 /* UIImageExtras.m in Sources */, - DA9FF584128A1F3600457759 /* AudioStreamerCUR.m in Sources */, 24ADC5121299F60C0014DB75 /* TiAppiOSProxy.m in Sources */, 24ADC5161299F6AA0014DB75 /* TiAppiOSBackgroundServiceProxy.m in Sources */, 84EB340017A8CAA700723B1E /* TiWindowProxy.m in Sources */, diff --git a/tests/Resources/ti.media.audioplayer.test.js b/tests/Resources/ti.media.audioplayer.test.js new file mode 100644 index 00000000000..46f0413e38b --- /dev/null +++ b/tests/Resources/ti.media.audioplayer.test.js @@ -0,0 +1,113 @@ +/* + * Appcelerator Titanium Mobile + * Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved. + * Licensed under the terms of the Apache Public License + * Please see the LICENSE included with this distribution for details. + */ +/* eslint-env mocha */ +/* global Ti */ +/* eslint no-unused-expressions: "off" */ +'use strict'; +var should = require('./utilities/assertions'); + +describe('Titanium.Media', function () { + it('#createAudioPlayer()', function () { + should(Ti.Media.createAudioPlayer).be.a.Function; + }); +}); + +describe('Titanium.Media.AudioPlayer', function () { + var audioPlayer; + + beforeEach(function () { + audioPlayer = Ti.Media.createAudioPlayer({ url: 'sample.mp3' }); + }); + + afterEach(function () { + audioPlayer = null; + }); + + it('apiName', function () { + should(audioPlayer).have.a.readOnlyProperty('apiName').which.is.a.String; + should(audioPlayer.apiName).be.eql('Ti.Media.AudioPlayer'); + }); + + // constants + it('STATE_BUFFERING', function () { + should(Ti.Media).have.constant('AUDIO_STATE_BUFFERING').which.is.a.Number; + }); + + it('STATE_INITIALIZED', function () { + should(Ti.Media).have.constant('AUDIO_STATE_INITIALIZED').which.is.a.Number; + }); + + it('STATE_PAUSED', function () { + should(Ti.Media).have.constant('AUDIO_STATE_PAUSED').which.is.a.Number; + }); + + it('STATE_PLAYING', function () { + should(Ti.Media).have.constant('AUDIO_STATE_PLAYING').which.is.a.Number; + }); + + it('STATE_STARTING', function () { + should(Ti.Media).have.constant('AUDIO_STATE_STARTING').which.is.a.Number; + }); + + it('STATE_STOPPED', function () { + should(Ti.Media).have.constant('AUDIO_STATE_STOPPED').which.is.a.Number; + }); + + it('STATE_STOPPING', function () { + should(Ti.Media).have.constant('AUDIO_STATE_STOPPING').which.is.a.Number; + }); + + it('STATE_WAITING_FOR_DATA', function () { + should(Ti.Media).have.constant('AUDIO_STATE_WAITING_FOR_DATA').which.is.a.Number; + }); + + it('STATE_WAITING_FOR_QUEUE', function () { + should(Ti.Media).have.constant('AUDIO_STATE_WAITING_FOR_QUEUE').which.is.a.Number; + }); + + it('.url', function () { + should(audioPlayer.url).be.a.String; + should(audioPlayer.getUrl).be.a.Function; + should(audioPlayer.setUrl).be.a.Function; + should(audioPlayer.url).eql(audioPlayer.getUrl()); + }); + + it('#start, #stop', function (finish) { + should(audioPlayer.start).be.a.Function; + should(audioPlayer.stop).be.a.Function; + + audioPlayer.start(); + + setTimeout(function () { + audioPlayer.stop(); + finish(); + }, 1000); + }); + + it('#pause', function (finish) { + should(audioPlayer.pause).be.a.Function; + + audioPlayer.start(); + + setTimeout(function () { + audioPlayer.pause(); + finish(); + }, 1000); + }); + + it('#restart', function (finish) { + should(audioPlayer.restart).be.a.Function; + + audioPlayer.start(); + + setTimeout(function () { + audioPlayer.restart(); + audioPlayer.stop(); + finish(); + }, 1000); + }); +});