aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Jordan2012-03-28 19:12:02 -0400
committerBrian Jordan2012-03-28 19:12:02 -0400
commitb9a950fcaf16c48942efb1e9e9f97cea359610be (patch)
treececdafade70262d34aeb6e2c3e3a0d1f6f9e30d4
parent691dd6be8a3924bc5cca09c5263945f4659ccac0 (diff)
downloadVideo-Tuneup-b9a950fcaf16c48942efb1e9e9f97cea359610be.tar.bz2
implement video export
-rw-r--r--Video Tuneup.xcodeproj/project.pbxproj16
-rw-r--r--Video Tuneup/SimpleEditor.h119
-rw-r--r--Video Tuneup/SimpleEditor.m525
-rw-r--r--Video Tuneup/ViewController.h7
-rw-r--r--Video Tuneup/ViewController.m112
-rw-r--r--Video Tuneup/en.lproj/ViewController_iPad.xib78
6 files changed, 849 insertions, 8 deletions
diff --git a/Video Tuneup.xcodeproj/project.pbxproj b/Video Tuneup.xcodeproj/project.pbxproj
index decab7a..622f1e3 100644
--- a/Video Tuneup.xcodeproj/project.pbxproj
+++ b/Video Tuneup.xcodeproj/project.pbxproj
@@ -8,6 +8,9 @@
/* Begin PBXBuildFile section */
D3531C94152380D800E286B8 /* song.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = D3531C93152380D800E286B8 /* song.mp3 */; };
+ D3531C9C1523B7BB00E286B8 /* SimpleEditor.m in Sources */ = {isa = PBXBuildFile; fileRef = D3531C9B1523B7BB00E286B8 /* SimpleEditor.m */; };
+ D3531CA01523B8DA00E286B8 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3531C9F1523B8DA00E286B8 /* QuartzCore.framework */; };
+ D3531CA21523BE2900E286B8 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3531CA11523BE2900E286B8 /* AssetsLibrary.framework */; };
D36CCD3615227D06003CCAFC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D36CCD3515227D06003CCAFC /* UIKit.framework */; };
D36CCD3815227D06003CCAFC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D36CCD3715227D06003CCAFC /* Foundation.framework */; };
D36CCD3A15227D06003CCAFC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D36CCD3915227D06003CCAFC /* CoreGraphics.framework */; };
@@ -40,6 +43,11 @@
/* Begin PBXFileReference section */
D3531C93152380D800E286B8 /* song.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = song.mp3; path = ../../../../code_others/python/remix/examples/music/song.mp3; sourceTree = "<group>"; };
+ D3531C9A1523B7BB00E286B8 /* SimpleEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimpleEditor.h; sourceTree = "<group>"; };
+ D3531C9B1523B7BB00E286B8 /* SimpleEditor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimpleEditor.m; sourceTree = "<group>"; };
+ D3531C9D1523B89200E286B8 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
+ D3531C9F1523B8DA00E286B8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
+ D3531CA11523BE2900E286B8 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; };
D36CCD3115227D05003CCAFC /* Video Tuneup.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Video Tuneup.app"; sourceTree = BUILT_PRODUCTS_DIR; };
D36CCD3515227D06003CCAFC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
D36CCD3715227D06003CCAFC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
@@ -72,6 +80,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ D3531CA21523BE2900E286B8 /* AssetsLibrary.framework in Frameworks */,
+ D3531CA01523B8DA00E286B8 /* QuartzCore.framework in Frameworks */,
D36CCD781522920D003CCAFC /* CoreMedia.framework in Frameworks */,
D36CCD6E15228700003CCAFC /* AVFoundation.framework in Frameworks */,
D36CCD3615227D06003CCAFC /* UIKit.framework in Frameworks */,
@@ -96,6 +106,9 @@
D36CCD2615227D05003CCAFC = {
isa = PBXGroup;
children = (
+ D3531CA11523BE2900E286B8 /* AssetsLibrary.framework */,
+ D3531C9F1523B8DA00E286B8 /* QuartzCore.framework */,
+ D3531C9D1523B89200E286B8 /* CoreAudio.framework */,
D36CCD771522920D003CCAFC /* CoreMedia.framework */,
D36CCD6D15228700003CCAFC /* AVFoundation.framework */,
D36CCD3B15227D06003CCAFC /* Video Tuneup */,
@@ -130,6 +143,8 @@
children = (
D36CCD6F1522893F003CCAFC /* Resources */,
D36CCD4415227D06003CCAFC /* AppDelegate.h */,
+ D3531C9A1523B7BB00E286B8 /* SimpleEditor.h */,
+ D3531C9B1523B7BB00E286B8 /* SimpleEditor.m */,
D36CCD4515227D06003CCAFC /* AppDelegate.m */,
D36CCD7615228B04003CCAFC /* Controllers */,
D36CCD7515228ADC003CCAFC /* Views */,
@@ -310,6 +325,7 @@
D36CCD4615227D06003CCAFC /* AppDelegate.m in Sources */,
D36CCD4915227D06003CCAFC /* ViewController.m in Sources */,
D36CCD7415228ACE003CCAFC /* PlayerView.m in Sources */,
+ D3531C9C1523B7BB00E286B8 /* SimpleEditor.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Video Tuneup/SimpleEditor.h b/Video Tuneup/SimpleEditor.h
new file mode 100644
index 0000000..1809c44
--- /dev/null
+++ b/Video Tuneup/SimpleEditor.h
@@ -0,0 +1,119 @@
+
+/*
+ File: SimpleEditor.h
+ Abstract: Demonstrates construction of AVComposition, AVAudioMix, and AVVideoComposition.
+
+ Version: 1.1
+
+ Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
+ Inc. ("Apple") in consideration of your agreement to the following
+ terms, and your use, installation, modification or redistribution of
+ this Apple software constitutes acceptance of these terms. If you do
+ not agree with these terms, please do not use, install, modify or
+ redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and
+ subject to these terms, Apple grants you a personal, non-exclusive
+ license, under Apple's copyrights in this original Apple software (the
+ "Apple Software"), to use, reproduce, modify and redistribute the Apple
+ Software, with or without modifications, in source and/or binary forms;
+ provided that if you redistribute the Apple Software in its entirety and
+ without modifications, you must retain this notice and the following
+ text and disclaimers in all such redistributions of the Apple Software.
+ Neither the name, trademarks, service marks or logos of Apple Inc. may
+ be used to endorse or promote products derived from the Apple Software
+ without specific prior written permission from Apple. Except as
+ expressly stated in this notice, no other rights or licenses, express or
+ implied, are granted by Apple herein, including but not limited to any
+ patent rights that may be infringed by your derivative works or by other
+ works in which the Apple Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis. APPLE
+ MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
+ THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
+ OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
+ MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
+ AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
+ STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ Copyright (C) 2010 Apple Inc. All Rights Reserved.
+
+ */
+
+#import <Foundation/Foundation.h>
+#import <AVFoundation/AVFoundation.h>
+#import <CoreMedia/CMTime.h>
+
+
+typedef enum {
+ SimpleEditorTransitionTypeNone,
+ SimpleEditorTransitionTypeCrossFade,
+ SimpleEditorTransitionTypePush
+} SimpleEditorTransitionType;
+
+
+@interface SimpleEditor : NSObject
+{
+ // Configuration
+
+ NSArray *_clips; // array of AVURLAssets
+ NSArray *_clipTimeRanges; // array of CMTimeRanges stored in NSValues.
+
+ AVURLAsset *_commentary;
+ CMTime _commentaryStartTime;
+
+ SimpleEditorTransitionType _transitionType;
+ CMTime _transitionDuration;
+
+ NSString *_titleText;
+
+
+ // Composition objects.
+
+ AVComposition *_composition;
+ AVVideoComposition *_videoComposition;
+ AVAudioMix *_audioMix;
+
+ AVPlayerItem *_playerItem;
+ AVSynchronizedLayer *_synchronizedLayer;
+}
+
+// Set these properties before building the composition objects.
+@property (nonatomic, retain) NSArray *clips;
+@property (nonatomic, retain) NSArray *clipTimeRanges;
+
+@property (nonatomic, retain) AVURLAsset *commentary;
+@property (nonatomic) CMTime commentaryStartTime;
+
+@property (nonatomic) SimpleEditorTransitionType transitionType;
+@property (nonatomic) CMTime transitionDuration;
+
+@property (nonatomic, retain) NSString *titleText;
+
+
+// Build the composition, videoComposition, and audioMix.
+// If the composition is being built for playback then a synchronized layer and player item are also constructed.
+// All of these objects can be retrieved all of these objects with the accessors below.
+// Calling buildCompositionObjectsForPlayback: will get rid of any previously created composition objects.
+- (void)buildCompositionObjectsForPlayback:(BOOL)forPlayback;
+
+@property (nonatomic, readonly, retain) AVComposition *composition;
+@property (nonatomic, readonly, retain) AVVideoComposition *videoComposition;
+@property (nonatomic, readonly, retain) AVAudioMix *audioMix;
+
+- (void)getPlayerItem:(AVPlayerItem**)playerItemOut andSynchronizedLayer:(AVSynchronizedLayer**)synchronizedLayerOut;
+// The synchronized layer contains a layer tree which is synchronized with the provided player item.
+// Inside the layer tree there is a playerLayer along with other layers related to titling.
+
+- (AVAssetImageGenerator*)assetImageGenerator;
+- (AVAssetExportSession*)assetExportSessionWithPreset:(NSString*)presetName;
+
+
+@end
diff --git a/Video Tuneup/SimpleEditor.m b/Video Tuneup/SimpleEditor.m
new file mode 100644
index 0000000..f666d6c
--- /dev/null
+++ b/Video Tuneup/SimpleEditor.m
@@ -0,0 +1,525 @@
+
+/*
+ File: SimpleEditor.m
+ Abstract: Demonstrates construction of AVComposition, AVAudioMix, and AVVideoComposition.
+
+ Version: 1.1
+
+ Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple
+ Inc. ("Apple") in consideration of your agreement to the following
+ terms, and your use, installation, modification or redistribution of
+ this Apple software constitutes acceptance of these terms. If you do
+ not agree with these terms, please do not use, install, modify or
+ redistribute this Apple software.
+
+ In consideration of your agreement to abide by the following terms, and
+ subject to these terms, Apple grants you a personal, non-exclusive
+ license, under Apple's copyrights in this original Apple software (the
+ "Apple Software"), to use, reproduce, modify and redistribute the Apple
+ Software, with or without modifications, in source and/or binary forms;
+ provided that if you redistribute the Apple Software in its entirety and
+ without modifications, you must retain this notice and the following
+ text and disclaimers in all such redistributions of the Apple Software.
+ Neither the name, trademarks, service marks or logos of Apple Inc. may
+ be used to endorse or promote products derived from the Apple Software
+ without specific prior written permission from Apple. Except as
+ expressly stated in this notice, no other rights or licenses, express or
+ implied, are granted by Apple herein, including but not limited to any
+ patent rights that may be infringed by your derivative works or by other
+ works in which the Apple Software may be incorporated.
+
+ The Apple Software is provided by Apple on an "AS IS" basis. APPLE
+ MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
+ THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
+ OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+
+ IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
+ MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
+ AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
+ STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ Copyright (C) 2010 Apple Inc. All Rights Reserved.
+
+ */
+
+#import "SimpleEditor.h"
+
+#import <CoreMedia/CoreMedia.h>
+#import <AVFoundation/AVFoundation.h>
+
+@interface SimpleEditor ()
+@property (nonatomic, readwrite, retain) AVComposition *composition;
+@property (nonatomic, readwrite, retain) AVVideoComposition *videoComposition;
+@property (nonatomic, readwrite, retain) AVAudioMix *audioMix;
+@property (nonatomic, readwrite, retain) AVPlayerItem *playerItem;
+@property (nonatomic, readwrite, retain) AVSynchronizedLayer *synchronizedLayer;
+
+@end
+
+
+@implementation SimpleEditor
+
+- (id)init
+{
+ if (self = [super init]) {
+ _commentaryStartTime = CMTimeMake(2, 1); // Default start time for the commentary is two seconds.
+
+ _transitionDuration = CMTimeMake(1, 1); // Default transition duration is one second.
+
+ // just until we have the UI for this wired up
+ NSMutableArray *clipTimeRanges = [[NSMutableArray alloc] initWithCapacity:3];
+ CMTimeRange defaultTimeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMake(5, 1));
+ NSValue *defaultTimeRangeValue = [NSValue valueWithCMTimeRange:defaultTimeRange];
+ [clipTimeRanges addObject:defaultTimeRangeValue];
+ [clipTimeRanges addObject:defaultTimeRangeValue];
+ [clipTimeRanges addObject:defaultTimeRangeValue];
+ _clipTimeRanges = clipTimeRanges;
+ }
+ return self;
+}
+
+// Configuration
+
+@synthesize clips = _clips, clipTimeRanges = _clipTimeRanges;
+@synthesize commentary = _commentary, commentaryStartTime = _commentaryStartTime;
+@synthesize transitionType = _transitionType, transitionDuration = _transitionDuration;
+@synthesize titleText = _titleText;
+
+// Composition objects.
+
+@synthesize composition = _composition;
+@synthesize videoComposition =_videoComposition;
+@synthesize audioMix = _audioMix;
+@synthesize playerItem = _playerItem;
+@synthesize synchronizedLayer = _synchronizedLayer;
+
+static CGImageRef createStarImage(CGFloat radius)
+{
+ int i, count = 5;
+#if TARGET_OS_IPHONE
+ CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
+#else // not TARGET_OS_IPHONE
+ CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
+#endif // not TARGET_OS_IPHONE
+ CGImageRef image = NULL;
+ size_t width = 2*radius;
+ size_t height = 2*radius;
+ size_t bytesperrow = width * 4;
+ CGContextRef context = CGBitmapContextCreate((void *)NULL, width, height, 8, bytesperrow, colorspace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
+ CGContextClearRect(context, CGRectMake(0, 0, 2*radius, 2*radius));
+ CGContextSetLineWidth(context, radius / 15.0);
+
+ for( i = 0; i < 2 * count; i++ ) {
+ CGFloat angle = i * M_PI / count;
+ CGFloat pointradius = (i % 2) ? radius * 0.37 : radius * 0.95;
+ CGFloat x = radius + pointradius * cos(angle);
+ CGFloat y = radius + pointradius * sin(angle);
+ if (i == 0)
+ CGContextMoveToPoint(context, x, y);
+ else
+ CGContextAddLineToPoint(context, x, y);
+ }
+ CGContextClosePath(context);
+
+ CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
+ CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);
+ CGContextDrawPath(context, kCGPathFillStroke);
+ CGColorSpaceRelease(colorspace);
+ image = CGBitmapContextCreateImage(context);
+ CGContextRelease(context);
+ return image;
+}
+
+- (void)buildSequenceComposition:(AVMutableComposition *)composition
+{
+ CMTime nextClipStartTime = kCMTimeZero;
+ NSInteger i;
+
+ // No transitions: place clips into one video track and one audio track in composition.
+ NSLog(@"Building sequence composition. Count is %i", [_clips count]);
+
+ AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
+ AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
+
+ for (i = 0; i < [_clips count]; i++ ) {
+ AVURLAsset *asset = [_clips objectAtIndex:i];
+ NSValue *clipTimeRange = [_clipTimeRanges objectAtIndex:i];
+ CMTimeRange timeRangeInAsset;
+ if (clipTimeRange)
+ timeRangeInAsset = [clipTimeRange CMTimeRangeValue];
+ else
+ timeRangeInAsset = CMTimeRangeMake(kCMTimeZero, [asset duration]);
+
+ AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
+ [compositionVideoTrack insertTimeRange:timeRangeInAsset ofTrack:clipVideoTrack atTime:nextClipStartTime error:nil];
+
+ NSLog(@"Composition audio?");
+ if ([[asset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
+ AVAssetTrack *clipAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
+ NSLog(@"Composition audio!");
+ [compositionAudioTrack insertTimeRange:timeRangeInAsset ofTrack:clipAudioTrack atTime:nextClipStartTime error:nil];
+ }
+
+ // Note: This is largely equivalent:
+ // [composition insertTimeRange:timeRangeInAsset ofAsset:asset atTime:nextClipStartTime error:NULL];
+ // except that if the video tracks dimensions do not match, additional video tracks will be added to the composition.
+
+ nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRangeInAsset.duration);
+ }
+}
+
+- (void)buildTransitionComposition:(AVMutableComposition *)composition andVideoComposition:(AVMutableVideoComposition *)videoComposition
+{
+ CMTime nextClipStartTime = kCMTimeZero;
+ NSInteger i;
+
+ // Make transitionDuration no greater than half the shortest clip duration.
+ CMTime transitionDuration = self.transitionDuration;
+ for (i = 0; i < [_clips count]; i++ ) {
+ NSValue *clipTimeRange = [_clipTimeRanges objectAtIndex:i];
+ if (clipTimeRange) {
+ CMTime halfClipDuration = [clipTimeRange CMTimeRangeValue].duration;
+ halfClipDuration.timescale *= 2; // You can halve a rational by doubling its denominator.
+ transitionDuration = CMTimeMinimum(transitionDuration, halfClipDuration);
+ }
+ }
+
+ // Add two video tracks and two audio tracks.
+ AVMutableCompositionTrack *compositionVideoTracks[2];
+ AVMutableCompositionTrack *compositionAudioTracks[2];
+ compositionVideoTracks[0] = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
+ compositionVideoTracks[1] = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
+ compositionAudioTracks[0] = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
+ compositionAudioTracks[1] = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
+
+ CMTimeRange *passThroughTimeRanges = alloca(sizeof(CMTimeRange) * [_clips count]);
+ CMTimeRange *transitionTimeRanges = alloca(sizeof(CMTimeRange) * [_clips count]);
+
+ // Place clips into alternating video & audio tracks in composition, overlapped by transitionDuration.
+ for (i = 0; i < [_clips count]; i++ ) {
+ NSInteger alternatingIndex = i % 2; // alternating targets: 0, 1, 0, 1, ...
+ AVURLAsset *asset = [_clips objectAtIndex:i];
+ NSValue *clipTimeRange = [_clipTimeRanges objectAtIndex:i];
+ CMTimeRange timeRangeInAsset;
+ if (clipTimeRange)
+ timeRangeInAsset = [clipTimeRange CMTimeRangeValue];
+ else
+ timeRangeInAsset = CMTimeRangeMake(kCMTimeZero, [asset duration]);
+
+ AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
+ [compositionVideoTracks[alternatingIndex] insertTimeRange:timeRangeInAsset ofTrack:clipVideoTrack atTime:nextClipStartTime error:nil];
+
+ AVAssetTrack *clipAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
+ [compositionAudioTracks[alternatingIndex] insertTimeRange:timeRangeInAsset ofTrack:clipAudioTrack atTime:nextClipStartTime error:nil];
+
+ // Remember the time range in which this clip should pass through.
+ // Every clip after the first begins with a transition.
+ // Every clip before the last ends with a transition.
+ // Exclude those transitions from the pass through time ranges.
+ passThroughTimeRanges[i] = CMTimeRangeMake(nextClipStartTime, timeRangeInAsset.duration);
+ if (i > 0) {
+ passThroughTimeRanges[i].start = CMTimeAdd(passThroughTimeRanges[i].start, transitionDuration);
+ passThroughTimeRanges[i].duration = CMTimeSubtract(passThroughTimeRanges[i].duration, transitionDuration);
+ }
+ if (i+1 < [_clips count]) {
+ passThroughTimeRanges[i].duration = CMTimeSubtract(passThroughTimeRanges[i].duration, transitionDuration);
+ }
+
+ // The end of this clip will overlap the start of the next by transitionDuration.
+ // (Note: this arithmetic falls apart if timeRangeInAsset.duration < 2 * transitionDuration.)
+ nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRangeInAsset.duration);
+ nextClipStartTime = CMTimeSubtract(nextClipStartTime, transitionDuration);
+
+ // Remember the time range for the transition to the next item.
+ transitionTimeRanges[i] = CMTimeRangeMake(nextClipStartTime, transitionDuration);
+ }
+
+ // Set up the video composition if we are to perform crossfade or push transitions between clips.
+ NSMutableArray *instructions = [NSMutableArray array];
+
+ // Cycle between "pass through A", "transition from A to B", "pass through B", "transition from B to A".
+ for (i = 0; i < [_clips count]; i++ ) {
+ NSInteger alternatingIndex = i % 2; // alternating targets
+
+ // Pass through clip i.
+ AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
+ passThroughInstruction.timeRange = passThroughTimeRanges[i];
+ AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[alternatingIndex]];
+
+ passThroughInstruction.layerInstructions = [NSArray arrayWithObject:passThroughLayer];
+ [instructions addObject:passThroughInstruction];
+
+ if (i+1 < [_clips count]) {
+ // Add transition from clip i to clip i+1.
+
+ AVMutableVideoCompositionInstruction *transitionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
+ transitionInstruction.timeRange = transitionTimeRanges[i];
+ AVMutableVideoCompositionLayerInstruction *fromLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[alternatingIndex]];
+ AVMutableVideoCompositionLayerInstruction *toLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[1-alternatingIndex]];
+
+ if (self.transitionType == SimpleEditorTransitionTypeCrossFade) {
+ // Fade out the fromLayer by setting a ramp from 1.0 to 0.0.
+ [fromLayer setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:transitionTimeRanges[i]];
+ }
+ else if (self.transitionType == SimpleEditorTransitionTypePush) {
+ // Set a transform ramp on fromLayer from identity to all the way left of the screen.
+ [fromLayer setTransformRampFromStartTransform:CGAffineTransformIdentity toEndTransform:CGAffineTransformMakeTranslation(-composition.naturalSize.width, 0.0) timeRange:transitionTimeRanges[i]];
+ // Set a transform ramp on toLayer from all the way right of the screen to identity.
+ [toLayer setTransformRampFromStartTransform:CGAffineTransformMakeTranslation(+composition.naturalSize.width, 0.0) toEndTransform:CGAffineTransformIdentity timeRange:transitionTimeRanges[i]];
+ }
+
+ transitionInstruction.layerInstructions = [NSArray arrayWithObjects:fromLayer, toLayer, nil];
+ [instructions addObject:transitionInstruction];
+ }
+ }
+
+ videoComposition.instructions = instructions;
+}
+
+- (void)addCommentaryTrackToComposition:(AVMutableComposition *)composition withAudioMix:(AVMutableAudioMix *)audioMix
+{
+ NSInteger i;
+ NSArray *tracksToDuck = [composition tracksWithMediaType:AVMediaTypeAudio]; // before we add the commentary
+
+ // Clip commentary duration to composition duration.
+ CMTimeRange commentaryTimeRange = CMTimeRangeMake(self.commentaryStartTime, self.commentary.duration);
+ if (CMTIME_COMPARE_INLINE(CMTimeRangeGetEnd(commentaryTimeRange), >, [composition duration]))
+ commentaryTimeRange.duration = CMTimeSubtract([composition duration], commentaryTimeRange.start);
+
+ // Add the commentary track.
+ AVMutableCompositionTrack *compositionCommentaryTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
+ [compositionCommentaryTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, commentaryTimeRange.duration) ofTrack:[[self.commentary tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:commentaryTimeRange.start error:nil];
+
+
+ NSMutableArray *trackMixArray = [NSMutableArray array];
+ CMTime rampDuration = CMTimeMake(1, 2); // half-second ramps
+ for (i = 0; i < [tracksToDuck count]; i++) {
+ AVMutableAudioMixInputParameters *trackMix = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:[tracksToDuck objectAtIndex:i]];
+ [trackMix setVolumeRampFromStartVolume:1.0 toEndVolume:0.2 timeRange:CMTimeRangeMake(CMTimeSubtract(commentaryTimeRange.start, rampDuration), rampDuration)];
+ [trackMix setVolumeRampFromStartVolume:0.2 toEndVolume:1.0 timeRange:CMTimeRangeMake(CMTimeRangeGetEnd(commentaryTimeRange), rampDuration)];
+ [trackMixArray addObject:trackMix];
+ }
+ audioMix.inputParameters = trackMixArray;
+}
+
+- (void)buildPassThroughVideoComposition:(AVMutableVideoComposition *)videoComposition forComposition:(AVMutableComposition *)composition
+{
+ // Make a "pass through video track" video composition.
+ AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
+ passThroughInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [composition duration]);
+
+ AVAssetTrack *videoTrack = [[composition tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
+ AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
+
+ passThroughInstruction.layerInstructions = [NSArray arrayWithObject:passThroughLayer];
+ videoComposition.instructions = [NSArray arrayWithObject:passThroughInstruction];
+}
+
+- (CALayer *)buildAnimatedTitleLayerForSize:(CGSize)videoSize
+{
+ // Create a layer for the overall title animation.
+ CALayer *animatedTitleLayer = [CALayer layer];
+
+ // Create a layer for the text of the title.
+ CATextLayer *titleLayer = [CATextLayer layer];
+ titleLayer.string = self.titleText;
+ titleLayer.font = @"Helvetica";
+ titleLayer.fontSize = videoSize.height / 6;
+ //?? titleLayer.shadowOpacity = 0.5;
+ titleLayer.alignmentMode = kCAAlignmentCenter;
+ titleLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height / 6);
+
+ // Add it to the overall layer.
+ [animatedTitleLayer addSublayer:titleLayer];
+
+ // Create a layer that contains a ring of stars.
+ CALayer *ringOfStarsLayer = [CALayer layer];
+
+ NSInteger starCount = 9, s;
+ CGFloat starRadius = videoSize.height / 10;
+ CGFloat ringRadius = videoSize.height * 0.8 / 2;
+ CGImageRef starImage = createStarImage(starRadius);
+ for (s = 0; s < starCount; s++) {
+ CALayer *starLayer = [CALayer layer];
+ CGFloat angle = s * 2 * M_PI / starCount;
+ starLayer.bounds = CGRectMake(0, 0, 2 * starRadius, 2 * starRadius);
+ starLayer.position = CGPointMake(ringRadius * cos(angle), ringRadius * sin(angle));
+// starLayer.contents = (id)starImage;
+ starLayer.contents = (__bridge id)starImage;
+ [ringOfStarsLayer addSublayer:starLayer];
+ }
+ CGImageRelease(starImage);
+
+ // Rotate the ring of stars.
+ CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
+ rotationAnimation.repeatCount = 1e100; // forever
+ rotationAnimation.fromValue = [NSNumber numberWithFloat:0.0];
+ rotationAnimation.toValue = [NSNumber numberWithFloat:2 * M_PI];
+ rotationAnimation.duration = 10.0; // repeat every 10 seconds
+ rotationAnimation.additive = YES;
+ rotationAnimation.removedOnCompletion = NO;
+ rotationAnimation.beginTime = 1e-100; // CoreAnimation automatically replaces zero beginTime with CACurrentMediaTime(). The constant AVCoreAnimationBeginTimeAtZero is also available.
+ [ringOfStarsLayer addAnimation:rotationAnimation forKey:nil];
+
+ // Add the ring of stars to the overall layer.
+ animatedTitleLayer.position = CGPointMake(videoSize.width / 2.0, videoSize.height / 2.0);
+ [animatedTitleLayer addSublayer:ringOfStarsLayer];
+
+ // Animate the opacity of the overall layer so that it fades out from 3 sec to 4 sec.
+ CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
+ fadeAnimation.fromValue = [NSNumber numberWithFloat:1.0];
+ fadeAnimation.toValue = [NSNumber numberWithFloat:0.0];
+ fadeAnimation.additive = NO;
+ fadeAnimation.removedOnCompletion = NO;
+ fadeAnimation.beginTime = 10.0;
+ fadeAnimation.duration = 2.0;
+ fadeAnimation.fillMode = kCAFillModeBoth;
+ [animatedTitleLayer addAnimation:fadeAnimation forKey:nil];
+
+ return animatedTitleLayer;
+}
+
+- (void)buildCompositionObjectsForPlayback:(BOOL)forPlayback
+{
+ NSLog(@"Building. Count is %i", [_clips count]);
+ CGSize videoSize = [[_clips objectAtIndex:0] naturalSize];
+ AVMutableComposition *composition = [AVMutableComposition composition];
+ AVMutableVideoComposition *videoComposition = nil;
+ AVMutableAudioMix *audioMix = nil;
+ CALayer *animatedTitleLayer = nil;
+
+ composition.naturalSize = videoSize;
+
+ if (self.transitionType == SimpleEditorTransitionTypeNone) {
+ // No transitions: place clips into one video track and one audio track in composition.
+
+ [self buildSequenceComposition:composition];
+ }
+ else {
+ // With transitions:
+ // Place clips into alternating video & audio tracks in composition, overlapped by transitionDuration.
+ // Set up the video composition to cycle between "pass through A", "transition from A to B",
+ // "pass through B", "transition from B to A".
+
+ videoComposition = [AVMutableVideoComposition videoComposition];
+ [self buildTransitionComposition:composition andVideoComposition:videoComposition];
+ }
+
+ // If one is provided, add a commentary track and duck all other audio during it.
+ if (self.commentary) {
+ // Add the commentary track and duck all other audio during it.
+
+ audioMix = [AVMutableAudioMix audioMix];
+ [self addCommentaryTrackToComposition:composition withAudioMix:audioMix];
+ }
+
+ // Set up Core Animation layers to contribute a title animation overlay if we have a title set.
+// if (self.titleText) {
+// animatedTitleLayer = [self buildAnimatedTitleLayerForSize:videoSize];
+//
+// if (! forPlayback) {
+// // For export: build a Core Animation tree that contains both the animated title and the video.
+// CALayer *parentLayer = [CALayer layer];
+// CALayer *videoLayer = [CALayer layer];
+// parentLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
+// videoLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
+// [parentLayer addSublayer:videoLayer];
+// [parentLayer addSublayer:animatedTitleLayer];
+//
+// if (! videoComposition) {
+// // No transition set -- make a "pass through video track" video composition so we can include the Core Animation tree as a post-processing stage.
+// videoComposition = [AVMutableVideoComposition videoComposition];
+//
+// [self buildPassThroughVideoComposition:videoComposition forComposition:composition];
+// }
+//
+// videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
+// }
+// }
+
+ if (videoComposition) {
+ // Every videoComposition needs these properties to be set:
+ videoComposition.frameDuration = CMTimeMake(1, 30); // 30 fps
+ videoComposition.renderSize = videoSize;
+ }
+
+ self.composition = composition;
+ self.videoComposition = videoComposition;
+ self.audioMix = audioMix;
+
+ self.synchronizedLayer = nil;
+
+ NSLog(@"Almost done building");
+
+ if (forPlayback) {
+#if TARGET_OS_EMBEDDED
+ // Render high-def movies at half scale for real-time playback (device-only).
+ if (videoSize.width > 640)
+ videoComposition.renderScale = 0.5;
+#endif // TARGET_OS_EMBEDDED
+
+ AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:composition];
+ playerItem.videoComposition = videoComposition;
+ playerItem.audioMix = audioMix;
+ self.playerItem = playerItem;
+
+// if (animatedTitleLayer) {
+// // Build an AVSynchronizedLayer that contains the animated title.
+// self.synchronizedLayer = [AVSynchronizedLayer synchronizedLayerWithPlayerItem:self.playerItem];
+// self.synchronizedLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
+// [self.synchronizedLayer addSublayer:animatedTitleLayer];
+// }
+ }
+}
+
+- (void)getPlayerItem:(AVPlayerItem**)playerItemOut andSynchronizedLayer:(AVSynchronizedLayer**)synchronizedLayerOut
+{
+ if (playerItemOut) {
+ *playerItemOut = _playerItem;
+ }
+ if (synchronizedLayerOut) {
+ *synchronizedLayerOut = _synchronizedLayer;
+ }
+}
+
+- (AVAssetImageGenerator*)assetImageGenerator
+{
+ AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:self.composition];
+ generator.videoComposition = self.videoComposition;
+ return generator;
+}
+
+- (AVAssetExportSession*)assetExportSessionWithPreset:(NSString*)presetName
+{
+ AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:self.composition presetName:presetName];
+ session.videoComposition = self.videoComposition;
+ session.audioMix = self.audioMix;
+// return [session autorelease];
+ return session;
+}
+
+//- (void)dealloc
+//{
+// [_clips release];
+// [_clipTimeRanges release];
+//
+// [_commentary release];
+// [_titleText release];
+//
+//
+// [_composition release];
+// [_videoComposition release];
+// [_audioMix release];
+//
+// [_playerItem release];
+// [_synchronizedLayer release];
+//
+// [super dealloc];
+//}
+
+@end
diff --git a/Video Tuneup/ViewController.h b/Video Tuneup/ViewController.h
index 9ad27d8..0f8279c 100644
--- a/Video Tuneup/ViewController.h
+++ b/Video Tuneup/ViewController.h
@@ -8,13 +8,17 @@
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
+#import <AssetsLibrary/AssetsLibrary.h>
+#import "SimpleEditor.h"
@class PlayerView;
@interface ViewController : UIViewController {
+ AVURLAsset *asset;
}
@property (nonatomic, retain) AVPlayer *player;
+@property (nonatomic, retain) SimpleEditor *editor;
@property (retain) AVPlayerItem *playerItem;
@property (nonatomic, retain) IBOutlet PlayerView *playerView;
@property (nonatomic, retain) IBOutlet UIButton *playButton;
@@ -26,5 +30,8 @@
- (IBAction)play:sender;
- (IBAction)pause:sender;
- (IBAction)rewind:sender;
+- (IBAction)edit:sender;
- (void)syncUI;
+
+- (void)exportDidFinish:(AVAssetExportSession*)session;
@end \ No newline at end of file
diff --git a/Video Tuneup/ViewController.m b/Video Tuneup/ViewController.m
index 0c4279a..fe509c2 100644
--- a/Video Tuneup/ViewController.m
+++ b/Video Tuneup/ViewController.m
@@ -8,13 +8,14 @@
#import "ViewController.h"
#import "PlayerView.h"
+#import "SimpleEditor.h"
// Define this constant for the key-value observation context.
static const NSString *ItemStatusContext;
@implementation ViewController
-@synthesize player, playerItem, playerView, playButton, pauseButton, rewindButton;
+@synthesize player, playerItem, playerView, playButton, pauseButton, rewindButton, editor;
#pragma mark - Video playback
@@ -40,7 +41,7 @@ static const NSString *ItemStatusContext;
NSURL *fileURL = [[NSBundle mainBundle]
URLForResource:@"airplane" withExtension:@"m4v"];
- AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
+ asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSLog(@"Asset duration is %f", CMTimeGetSeconds([asset duration]));
@@ -103,13 +104,116 @@ static const NSString *ItemStatusContext;
}
- (IBAction)pause:sender {
+ NSLog(@"Pausing...");
+
[player pause];
}
- (IBAction)rewind:sender {
[player seekToTime:kCMTimeZero];
}
+
+- (IBAction)edit:sender {
+
+ NSLog(@"Editing...");
+
+ // Initialize video editor
+ self.editor = [[SimpleEditor alloc] init];
+
+ NSMutableArray *clips = [NSMutableArray arrayWithCapacity:3];
+
+ if(asset) {
+ [clips addObject:asset];
+ [clips addObject:asset];
+ [clips addObject:asset];
+ } else {NSLog(@"Error! No Asset!"); return;}
+
+ // Copy clips into editor
+// self.editor.clips = [clips copy];
+ self.editor.clips = clips;
+
+ NSLog(@"Put clips in. Count is %i", [clips count]);
+
+ // Begin export
+ [self.editor buildCompositionObjectsForPlayback:NO];
+ NSLog(@"Put clips in. Build.");
+ AVAssetExportSession *session = [self.editor assetExportSessionWithPreset:AVAssetExportPresetHighestQuality];
+ NSLog(@"Session");
+
+ NSLog(@"begin export");
+ NSString *filePath = nil;
+ NSUInteger count = 0;
+ do {
+ NSLog(@"Filepath");
+ filePath = NSTemporaryDirectory();
+
+ NSString *numberString = count > 0 ? [NSString stringWithFormat:@"-%i", count] : @"";
+ filePath = [filePath stringByAppendingPathComponent:[NSString stringWithFormat:@"Output-%@.mov", numberString]];
+ count++;
+ } while([[NSFileManager defaultManager] fileExistsAtPath:filePath]);
+
+ NSLog(@"Setting stuff.");
+
+ session.outputURL = [NSURL fileURLWithPath:filePath];
+ session.outputFileType = AVFileTypeQuickTimeMovie;
+
+ NSLog(@"Exporting asynchronously %i.", [clips count]);
+
+ [session exportAsynchronouslyWithCompletionHandler:^
+ {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ NSLog(@"Finished exporting.");
+ [self exportDidFinish:session];
+ });
+ }];
+}
+
+- (void)exportDidFinish:(AVAssetExportSession*)session
+{
+ NSLog(@"Finished export, attempting photo album");
+
+ NSURL *outputURL = session.outputURL;
+
+// _exporting = NO;
+// NSIndexPath *exportCellIndexPath = [NSIndexPath indexPathForRow:2 inSection:kProjectSection];
+// ExportCell *cell = (ExportCell*)[self.tableView cellForRowAtIndexPath:exportCellIndexPath];
+// cell.progressView.progress = 1.0;
+// [cell setProgressViewHidden:YES animated:YES];
+// [self updateCell:cell forRowAtIndexPath:exportCellIndexPath];
+
+ ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
+ if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL]) {
+ [library writeVideoAtPathToSavedPhotosAlbum:outputURL
+ completionBlock:^(NSURL *assetURL, NSError *error){
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (error) {
+ NSLog(@"writeVideoToAssestsLibrary failed: %@", error);
+ UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[error localizedDescription]
+ message:[error localizedRecoverySuggestion]
+ delegate:nil
+ cancelButtonTitle:@"OK"
+ otherButtonTitles:nil];
+ [alertView show];
+// [alertView release];
+ }
+ else {
+ NSLog(@"Completed photo album add");
+// _showSavedVideoToAssestsLibrary = YES;
+// ExportCell *cell = (ExportCell*)[self.tableView cellForRowAtIndexPath:exportCellIndexPath];
+// [cell setDetailTextLabelHidden:NO animated:YES];
+// [self updateCell:cell forRowAtIndexPath:exportCellIndexPath];
+// NSArray *modes = [[[NSArray alloc] initWithObjects:NSDefaultRunLoopMode, UITrackingRunLoopMode, nil] autorelease];
+// [self performSelector:@selector(hideCameraRollText) withObject:nil afterDelay:5.0 inModes:modes];
+ }
+ });
+
+ }];
+ } else {
+ NSLog(@"Video format is not compatible with saved photos album.");
+ }
+}
+
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[player seekToTime:kCMTimeZero];
}
@@ -131,9 +235,9 @@ static const NSString *ItemStatusContext;
NSLog(@"viewDidLoad");
- [self syncUI];
-
+ // Sync video player controls
NSLog(@"syncUI");
+ [self syncUI];
// Register with the notification center after creating the player item.
[[NSNotificationCenter defaultCenter]
diff --git a/Video Tuneup/en.lproj/ViewController_iPad.xib b/Video Tuneup/en.lproj/ViewController_iPad.xib
index c914faa..89c22e1 100644
--- a/Video Tuneup/en.lproj/ViewController_iPad.xib
+++ b/Video Tuneup/en.lproj/ViewController_iPad.xib
@@ -97,9 +97,27 @@
<object class="IBUIView" id="1061865793">
<reference key="NSNextResponder" ref="766721923"/>
<int key="NSvFlags">319</int>
- <string key="NSFrame">{{181, 369}, {384, 502}}</string>
+ <string key="NSFrame">{{174, 217}, {395, 371}}</string>
<reference key="NSSuperview" ref="766721923"/>
<reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="440536013"/>
+ <string key="NSReuseIdentifierKey">_NS:212</string>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MC44MDAwMDAwMTE5IDEgMC40MDAwMDAwMDYAA</bytes>
+ </object>
+ <object class="IBUIAccessibilityConfiguration" key="IBUIAccessibilityConfiguration">
+ <integer value="512" key="IBUIAccessibilityTraits"/>
+ </object>
+ <string key="targetRuntimeIdentifier">IBIPadFramework</string>
+ </object>
+ <object class="IBUIView" id="440536013">
+ <reference key="NSNextResponder" ref="766721923"/>
+ <int key="NSvFlags">319</int>
+ <string key="NSFrame">{{174, 613}, {395, 371}}</string>
+ <reference key="NSSuperview" ref="766721923"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
<string key="NSReuseIdentifierKey">_NS:212</string>
<object class="NSColor" key="IBUIBackgroundColor">
<int key="NSColorSpace">1</int>
@@ -156,13 +174,36 @@
<reference key="IBUIFontDescription" ref="165390451"/>
<reference key="IBUIFont" ref="564959921"/>
</object>
+ <object class="IBUIButton" id="739741806">
+ <reference key="NSNextResponder" ref="766721923"/>
+ <int key="NSvFlags">301</int>
+ <string key="NSFrame">{{89, 580}, {72, 37}}</string>
+ <reference key="NSSuperview" ref="766721923"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="1061865793"/>
+ <string key="NSReuseIdentifierKey">_NS:241</string>
+ <bool key="IBUIOpaque">NO</bool>
+ <string key="targetRuntimeIdentifier">IBIPadFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ <int key="IBUIButtonType">1</int>
+ <string key="IBUINormalTitle">Edit</string>
+ <reference key="IBUIHighlightedTitleColor" ref="804940373"/>
+ <object class="NSColor" key="IBUINormalTitleColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA</bytes>
+ </object>
+ <reference key="IBUINormalTitleShadowColor" ref="752673392"/>
+ <reference key="IBUIFontDescription" ref="165390451"/>
+ <reference key="IBUIFont" ref="564959921"/>
+ </object>
<object class="IBUIButton" id="913102771">
<reference key="NSNextResponder" ref="766721923"/>
<int key="NSvFlags">301</int>
<string key="NSFrame">{{498, 148}, {78, 37}}</string>
<reference key="NSSuperview" ref="766721923"/>
<reference key="NSWindow"/>
- <reference key="NSNextKeyView" ref="1061865793"/>
+ <reference key="NSNextKeyView" ref="739741806"/>
<string key="NSReuseIdentifierKey">_NS:241</string>
<bool key="IBUIOpaque">NO</bool>
<string key="targetRuntimeIdentifier">IBIPadFramework</string>
@@ -296,6 +337,15 @@
</object>
<int key="connectionID">27</int>
</object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchEventConnection" key="connection">
+ <string key="label">edit:</string>
+ <reference key="source" ref="739741806"/>
+ <reference key="destination" ref="841351856"/>
+ <int key="IBEventType">7</int>
+ </object>
+ <int key="connectionID">31</int>
+ </object>
</array>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
@@ -320,12 +370,14 @@
<int key="objectID">2</int>
<reference key="object" ref="766721923"/>
<array class="NSMutableArray" key="children">
- <reference ref="1061865793"/>
<reference ref="1049445720"/>
<reference ref="761978491"/>
<reference ref="380079406"/>
<reference ref="959867271"/>
<reference ref="913102771"/>
+ <reference ref="1061865793"/>
+ <reference ref="440536013"/>
+ <reference ref="739741806"/>
</array>
<reference key="parent" ref="0"/>
</object>
@@ -364,6 +416,16 @@
<reference key="object" ref="761978491"/>
<reference key="parent" ref="766721923"/>
</object>
+ <object class="IBObjectRecord">
+ <int key="objectID">28</int>
+ <reference key="object" ref="440536013"/>
+ <reference key="parent" ref="766721923"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">29</int>
+ <reference key="object" ref="739741806"/>
+ <reference key="parent" ref="766721923"/>
+ </object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
@@ -377,6 +439,9 @@
<string key="14.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="25.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="28.CustomClassName">PlayerView</string>
+ <string key="28.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="29.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="4.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
<string key="7.CustomClassName">PlayerView</string>
<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
@@ -385,7 +450,7 @@
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
- <int key="maxID">27</int>
+ <int key="maxID">31</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -401,6 +466,7 @@
<string key="className">ViewController</string>
<string key="superclassName">UIViewController</string>
<dictionary class="NSMutableDictionary" key="actions">
+ <string key="edit:">id</string>
<string key="loadAssetFromFile:">id</string>
<string key="loadAudioFromFile:">id</string>
<string key="pause:">id</string>
@@ -408,6 +474,10 @@
<string key="rewind:">id</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="actionInfosByName">
+ <object class="IBActionInfo" key="edit:">
+ <string key="name">edit:</string>
+ <string key="candidateClassName">id</string>
+ </object>
<object class="IBActionInfo" key="loadAssetFromFile:">
<string key="name">loadAssetFromFile:</string>
<string key="candidateClassName">id</string>