Skip to content
This repository has been archived by the owner on Aug 14, 2019. It is now read-only.

Added JSQAudioMediaItem class #1495

Merged
merged 10 commits into from Apr 5, 2016
14 changes: 14 additions & 0 deletions JSQMessages.xcodeproj/project.pbxproj
Expand Up @@ -7,6 +7,9 @@
objects = {

/* Begin PBXBuildFile section */
54271E3B1C90469100294290 /* jsq_messages_sample.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 54271E3A1C90469100294290 /* jsq_messages_sample.m4a */; };
54271E3E1C905B9200294290 /* JSQAudioMediaItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 54271E3D1C905B9200294290 /* JSQAudioMediaItem.m */; };
54271E401C905D1600294290 /* JSQAudioMediaItemTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54271E3F1C905D1600294290 /* JSQAudioMediaItemTests.m */; };
88078A9D19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 88078A9C19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m */; };
88324C3419F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 88324C3319F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m */; };
883C11781A09FB100092A16D /* JSQMessagesCellTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 883C11771A09FB100092A16D /* JSQMessagesCellTextView.m */; };
Expand Down Expand Up @@ -123,6 +126,10 @@
0B4D05069814EB50FB0F4229 /* Pods-JSQMessagesTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JSQMessagesTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-JSQMessagesTests/Pods-JSQMessagesTests.release.xcconfig"; sourceTree = "<group>"; };
1D4D3B82D90888BCEAF890D3 /* Pods-JSQMessagesTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JSQMessagesTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-JSQMessagesTests/Pods-JSQMessagesTests.debug.xcconfig"; sourceTree = "<group>"; };
3BA6237809BE0D008CFE3697 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
54271E3A1C90469100294290 /* jsq_messages_sample.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = jsq_messages_sample.m4a; sourceTree = "<group>"; };
54271E3C1C905B9200294290 /* JSQAudioMediaItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQAudioMediaItem.h; sourceTree = "<group>"; };
54271E3D1C905B9200294290 /* JSQAudioMediaItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQAudioMediaItem.m; sourceTree = "<group>"; };
54271E3F1C905D1600294290 /* JSQAudioMediaItemTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQAudioMediaItemTests.m; sourceTree = "<group>"; };
88078A9B19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSQMessagesMediaPlaceholderView.h; sourceTree = "<group>"; };
88078A9C19D8FEB5005B4595 /* JSQMessagesMediaPlaceholderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaPlaceholderView.m; sourceTree = "<group>"; };
88324C3319F6301C00BC732D /* JSQMessagesMediaViewBubbleImageMaskerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSQMessagesMediaViewBubbleImageMaskerTests.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -381,6 +388,7 @@
88A25F2B19D8DF2500924534 /* JSQMessagesDemo */ = {
isa = PBXGroup;
children = (
54271E3A1C90469100294290 /* jsq_messages_sample.m4a */,
88A25F2C19D8DF2500924534 /* AppDelegate.h */,
88A25F2D19D8DF2500924534 /* AppDelegate.m */,
88A25FD919D8E0C400924534 /* DemoMessagesViewController.h */,
Expand Down Expand Up @@ -493,6 +501,8 @@
88A25F7619D8E01A00924534 /* Model */ = {
isa = PBXGroup;
children = (
54271E3C1C905B9200294290 /* JSQAudioMediaItem.h */,
54271E3D1C905B9200294290 /* JSQAudioMediaItem.m */,
88445B3E19E1B4470014F889 /* JSQLocationMediaItem.h */,
88445B3F19E1B4470014F889 /* JSQLocationMediaItem.m */,
88A901B419F618B100F99777 /* JSQMediaItem.h */,
Expand Down Expand Up @@ -598,6 +608,7 @@
88A25FF219D8E18400924534 /* ModelTests */ = {
isa = PBXGroup;
children = (
54271E3F1C905D1600294290 /* JSQAudioMediaItemTests.m */,
88C00A4D1A44D4C600B004B3 /* JSQLocationMediaItemTests.m */,
88A25FF319D8E18400924534 /* JSQMessageMediaTests.m */,
88A25FF419D8E18400924534 /* JSQMessagesAvatarImageTests.m */,
Expand Down Expand Up @@ -729,6 +740,7 @@
88A25FCD19D8E01A00924534 /* JSQMessagesCollectionViewCellIncoming.xib in Resources */,
88A25FD619D8E01A00924534 /* JSQMessagesToolbarContentView.xib in Resources */,
88A25F3A19D8DF2500924534 /* Images.xcassets in Resources */,
54271E3B1C90469100294290 /* jsq_messages_sample.m4a in Resources */,
88A25FBC19D8E01A00924534 /* JSQMessagesViewController.xib in Resources */,
88A25FD819D8E01A00924534 /* JSQMessagesTypingIndicatorFooterView.xib in Resources */,
88A25F3919D8DF2500924534 /* Main.storyboard in Resources */,
Expand Down Expand Up @@ -870,6 +882,7 @@
88A25FE019D8E0C400924534 /* DemoModelData.m in Sources */,
88A25F3C19D8DF2500924534 /* main.m in Sources */,
88A25F3719D8DF2500924534 /* AppDelegate.m in Sources */,
54271E3E1C905B9200294290 /* JSQAudioMediaItem.m in Sources */,
886FFD2E19E9A65D00EB8485 /* UIDevice+JSQMessages.m in Sources */,
88B5C41F1B7C422900EC79D4 /* JSQMessagesBubblesSizeCalculator.m in Sources */,
88A25FB619D8E01A00924534 /* NSString+JSQMessages.m in Sources */,
Expand Down Expand Up @@ -900,6 +913,7 @@
88C00A521A44D4E500B004B3 /* JSQVideoMediaItemTests.m in Sources */,
88A2601519D8E18400924534 /* JSQMessagesInputToolbarTests.m in Sources */,
88A2601719D8E18400924534 /* JSQMessagesLoadEarlierHeaderViewTests.m in Sources */,
54271E401C905D1600294290 /* JSQAudioMediaItemTests.m in Sources */,
88A2601219D8E18400924534 /* JSQMessagesCollectionViewCellTests.m in Sources */,
88A2601619D8E18400924534 /* JSQMessagesLabelTests.m in Sources */,
88A2600B19D8E18400924534 /* JSQMessagesCollectionViewFlowLayoutTests.m in Sources */,
Expand Down
14 changes: 14 additions & 0 deletions JSQMessagesDemo/DemoModelData.m
Expand Up @@ -131,6 +131,7 @@ - (void)loadFakeMessages
nil];

[self addPhotoMediaMessage];
[self addAudioMediaMessage];

/**
* Setting to load extra messages for testing/demo
Expand All @@ -156,6 +157,19 @@ - (void)loadFakeMessages
}
}

- (void)addAudioMediaMessage
{
NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"];
NSData * audioData = [NSData dataWithContentsOfFile:sample];
JSQAudioMediaItem *audioItem = [[JSQAudioMediaItem alloc] initWithData:audioData];
// NSURL * audioURL = [NSURL fileURLWithPath:sample];
// JSQAudioMediaItem *audioItem = [[JSQAudioMediaItem alloc] initWithURL:audioURL isReadyToPlay:YES];
JSQMessage *audioMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires
displayName:kJSQDemoAvatarDisplayNameSquires
media:audioItem];
[self.messages addObject:audioMessage];
}

- (void)addPhotoMediaMessage
{
JSQPhotoMediaItem *photoItem = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage imageNamed:@"goldengate"]];
Expand Down
Binary file added JSQMessagesDemo/jsq_messages_sample.m4a
Binary file not shown.
77 changes: 77 additions & 0 deletions JSQMessagesTests/ModelTests/JSQAudioMediaItemTests.m
@@ -0,0 +1,77 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// MIT License
// Copyright (c) 2014 Jesse Squires
// http://opensource.org/licenses/MIT
//

#import <XCTest/XCTest.h>

#import "JSQAudioMediaItem.h"

@interface JSQAudioMediaItemTests : XCTestCase

@end

@implementation JSQAudioMediaItemTests

- (void)setUp
{
[super setUp];
}

- (void)tearDown
{
[super tearDown];
}

- (void)testAudioItemInit
{
JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] initWithData:[NSData data]];
XCTAssertNotNil(item);
}

- (void)testAudioItemIsEqual
{
NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"];
JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] initWithData:[NSData dataWithContentsOfFile:sample]];

JSQAudioMediaItem *copy = [item copy];

XCTAssertEqualObjects(item, copy, @"Copied items should be equal");

XCTAssertEqual([item hash], [copy hash], @"Copied item hashes should be equal");

XCTAssertEqualObjects(item, item, @"Item should be equal to itself");
}

- (void)testAudioItemArchiving
{
NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"];
JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] initWithData:[NSData dataWithContentsOfFile:sample]];

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:item];

JSQAudioMediaItem *unarchivedItem = [NSKeyedUnarchiver unarchiveObjectWithData:data];

XCTAssertEqualObjects(item, unarchivedItem);
}

- (void)testMediaDataProtocol
{
JSQAudioMediaItem *item = [[JSQAudioMediaItem alloc] initWithData:nil];

XCTAssertTrue(!CGSizeEqualToSize([item mediaViewDisplaySize], CGSizeZero));
XCTAssertNotNil([item mediaPlaceholderView]);
XCTAssertNil([item mediaView], @"Media view should be nil if image is nil");

NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"];
item.audioData = [NSData dataWithContentsOfFile:sample];

XCTAssertNotNil([item mediaView], @"Media view should NOT be nil once item has media data");
}

@end
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions JSQMessagesViewController/Categories/UIImage+JSQMessages.h
Expand Up @@ -76,4 +76,9 @@
*/
+ (UIImage *)jsq_defaultPlayImage;

/**
* @return The default pause icon image.
*/
+ (UIImage *)jsq_defaultPauseImage;

@end
5 changes: 5 additions & 0 deletions JSQMessagesViewController/Categories/UIImage+JSQMessages.m
Expand Up @@ -100,4 +100,9 @@ + (UIImage *)jsq_defaultPlayImage
return [UIImage jsq_bubbleImageFromBundleWithName:@"play"];
}

+ (UIImage *)jsq_defaultPauseImage
{
return [UIImage jsq_bubbleImageFromBundleWithName:@"pause"];
}

@end
1 change: 1 addition & 0 deletions JSQMessagesViewController/JSQMessages.h
Expand Up @@ -44,6 +44,7 @@
#import "JSQMessage.h"

#import "JSQMediaItem.h"
#import "JSQAudioMediaItem.h"
#import "JSQPhotoMediaItem.h"
#import "JSQLocationMediaItem.h"
#import "JSQVideoMediaItem.h"
Expand Down
124 changes: 124 additions & 0 deletions JSQMessagesViewController/Model/JSQAudioMediaItem.h
@@ -0,0 +1,124 @@
//
// JSQAudioMediaItem.h
//

#import "JSQMediaItem.h"

#import "AVFoundation/AVFoundation.h"

@class JSQAudioMediaItem;
@protocol JSQAudioMediaItemDelegate <NSObject>
@optional
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make these non-optional


/* didChangeOriginalAudioCategory is called if JSQAudioMediaItem changes the sound category or categoryOptions */
- (void)audioMediaItem:(JSQAudioMediaItem*)audioMediaItem didChangeOriginalAudioCategory:(NSString *)category originalOptions:(AVAudioSessionCategoryOptions)options;

/* didNotChangeCategory is called if JSQAudioMediaItem fails to change the category or categoryOptions */
- (void)audioMediaItem:(JSQAudioMediaItem*)audioMediaItem didNotChangeCategory:(NSError*)error;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be a single method?

audioMediaItem: didChangeAudioCategory: initialOptions: error:


@end

/**
* The `JSQAudioMediaItem` class is a concrete `JSQMediaItem` subclass that implements the `JSQMessageMediaData` protocol
* and represents a video media message. An initialized `JSQAudioMediaItem` object can be passed
* to a `JSQMediaMessage` object during its initialization to construct a valid media message object.
* You may wish to subclass `JSQAudioMediaItem` to provide additional functionality or behavior.
*/
@interface JSQAudioMediaItem : JSQMediaItem <JSQMessageMediaData, AVAudioPlayerDelegate, NSCoding, NSCopying>

/**
* delegate used for notification of audio events
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delegate object for audio event notifications

*/
@property (nonatomic, weak) id<JSQAudioMediaItemDelegate> delegate;

/**
* The URL that identifies a video resource.
*/
@property (nonatomic, strong) NSURL *audioURL;

/**
* An NSData object that contains an audio resource.
*/
@property (nonatomic, strong) NSData *audioData;

/**
* A boolean value that specifies whether or not the audio is ready to be played.
*
* @discussion When set to `YES`, the audio is ready. When set to `NO` it is not ready.
*/
@property (nonatomic, assign) BOOL isReadyToPlay;

/**
* A UIImage to be used for the play button. A default value will be used if not set.
*/
@property (strong, nonatomic) UIImage * playButtonImage;

/**
* A UIImage to be used for the pause button. A default value will be used if not set.
*/
@property (strong, nonatomic) UIImage * pauseButtonImage;

/**
* A UIFont to be used for the elapsed time label. A system font will be used if not set.
*/
@property (strong, nonatomic) UIFont * labelFont;

/**
* A UIColor to be used for the player's background.
*/
@property (strong, nonatomic) UIColor * backgroundColor;

/**
* A UIColor to be used for the player's tint.
*/
@property (strong, nonatomic) UIColor * tintColor;

/**
* UIEdgeInsets used to determine padding around the play/pause button and timer label
*/
@property (nonatomic) UIEdgeInsets controlInsets;

/**
* CGFloat used to determine padding between the button, progress bar, and label
*/
@property (nonatomic) CGFloat controlPadding;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's bundle up everything from playButtonImage to here in a new object, JSQAudioMediaViewConfiguration

This class can then own a config and apply it to the view.


/**
* Audio Category set prior to playback. Original value
*/
@property (nonatomic) NSString * audioCategory;

/**
* Audio Category options set prior to playback
*/
@property (nonatomic) AVAudioSessionCategoryOptions audioCategoryOptions;

/**
* Initializes and returns an audio media item having the given audioURL.
*
* @param audioURL The URL that identifies the audio resource.
* @param isReadyToPlay A boolean value that specifies if the audio is ready to play.
*
* @return An initialized `JSQAudioMediaItem` if successful, `nil` otherwise.
*
* @discussion If the audio must be downloaded from the network,
* you may initialize a `JSQAudioMediaItem` with a `nil` audioURL or specify `NO` for
* isReadyToPlay. Once the audio has been saved to disk, or is ready to stream, you can
* set the audioURL property or isReadyToPlay property, respectively.
*/
- (instancetype)initWithURL:(NSURL *)audioURL isReadyToPlay:(BOOL)isReadyToPlay;

/**
* Initializes and returns a audio media item having the given audioData.
*
* @param audioData The data object that contains the audio resource.
*
* @return An initialized `JSQAudioMediaItem` if successful, `nil` otherwise.
*
* @discussion If the audio must be downloaded from the network,
* you may initialize a `JSQVideoMediaItem` with a `nil` audioData.
* Once the audio is available you can set the file data property.
*/
- (instancetype)initWithData:(NSData *)fileData;

@end