# Kamcord 0.9.4 ## Code The code is available at the following git repository: https://github.com/kamcord/cocos2d-kamcord-ios. Please add yourself as a watcher since we frequently release new features and patches. ## Introduction Kamcord is a built-in gameplay video and audio recording technology for iOS. This repository contains a Kamcord SDK that works with cocos2d-1.0.1 and allows you, the game developer, to capture gameplay videos with a very simple API. Your users can then replay and share these gameplay videos via YouTube, Facebook, Twitter, and email. In order to use Kamcord, you need a developer key and developer secret. To get these, please sign up at http://kamcord.com/signup. **Kamcord works on iOS 5+ and gracefully turns itself off on iOS 4**. You can still run without problems on versions of iOS before iOS 5, though you will not be able to to record video. Kamcord works on the iPhone 3GS, iPhone 4, iPhone 4S, iPod Touch 3G and 4G, and all iPads. We will be making lots of improvements and adding many features over the next few months. We'd love to hear your feedback and thoughts. If you have any questions or comments, please don't hesitate to email or call Matt at matt@kamcord.com (650.267.1051). ## Gameplay Recordings Check out some gameplays recorded with Kamcord:
- Mr. Ball (Apple App Store)
- Platform Hell (Apple App Store)
- Adhesion (Apple App Store)
- Sewar Wars (Website)
- Carl The Spider (Apple App Store)
- Atlantis Oceans (Apple App Store)
- From
Frameworks
, drag and dropKamcord.framework
andAWSiOSSDK.framework
into your project. - Drag and drop the files under
Frameworks/Resources
to your project. For both this and the previous step, make sure to check the box next to the target application you want to link these frameworks and resources to (your game, presumably). - Ensure you have the following frameworks under
Build Phases
==>Link Binary With Libraries
:- Accounts
- AVFoundation
- AWSiOSSDK
- CoreData
- CoreGraphics
- CoreMedia
- CoreVideo
- Foundation
- Kamcord
- MediaPlayer
- MessageUI
- OpenGLES
- QuartzCore
- Security
- SystemConfiguration
- UIKit
To support iOS 4 deployment, set the frameworks inside the orange box to
Optional
. This will allow your app to run on devices with iOS 4 and ensures Kamcord functionality will gracefully silence itself on iOS 4 as if you had never installed Kamcord. - Add the following to
Build Settings
==>Other Linker Flags
:- -ObjC
- -all_load
- -lxml2
-
Import Kamcord into your application delegate:
#import <Kamcord/Kamcord.h>
- We will provide you with a per-game Kamcord developer key and developer secret. Please set them along with your app name when your app initializes or recording won't work.
[Kamcord setDeveloperKey:@"My_Developer_Key" developerSecret:@"My_Developer_Secret" appName:@"My_Game_Name"];
-
In your application delegate (or wherever you create the
UIWindow
and initializeCCDirector
), instantiate aKCGLView
. This is our special sublcass ofEAGLView
that aids in video recording. Then setwindow.rootViewController
to an instance ofKCViewController
and setwindow.rootViewController.view
to yourKCGLView
. If you have any other view controllers, comment them out.Also be sure to use
[Kamcord setDeviceOrientation:...]
instead of[director setDeviceOrientation:...]
:// Instantiate a KCGLView, which is a subclass with EAGLView with // special recording functionality. KCGLView * glView = [KCGLView viewWithFrame:[window bounds] pixelFormat:kEAGLColorFormatRGB565 depthFormat:0]; // Kamcord uses UIKit for autorotation, which requires special logic to handle rotations. window.rootViewController = [[KCViewController alloc] initWithNibName:nil bundle:nil]; window.rootViewController.view = glView; // Tell Kamcord about the root view controller and the KCGLView [Kamcord setParentViewController:window.rootViewController]; [Kamcord setOpenGLView:glView]; // Set the device orientation. Must use Kamcord, not CCDirector! [Kamcord setDeviceOrientation:CCDeviceOrientationLandscapeLeft];
This must all be done before:
[window addSubview:glView]; [window makeKeyAndVisible];
The full examples further below lay this out very clearly.
[[CCDirector sharedDirector] deviceOrientation]
, replace all of those calls with [Kamcord deviceOrientation]
.
If you set either `CCDeviceOrientationLandscapeLeft` or `CCDeviceOrientationLandscapeRight`, Kamcord will autorotate the screen to support both landscape orientations.
### Developer Settings
A YouTube video looks like this:
You can set the title, description, and keywords (highlighted in the orange boxes) with the following function:
+ (void) setYouTubeTitle:(NSString *)title
description:(NSString *)description
keywords:(NSString *)keywords;
`youtubeKeywords` is one string of word tokens delimited by commas (e.g. `"multi-word keyword, another multiword keyword, keyword3, keyword4"`).
A Facebook wall post looks like the following:
The `Message` is the text the user will enter. You can set the title, caption, and description with the following function:
+ (void) setFacebookTitle:(NSString *)title
caption:(NSString *)caption
description:(NSString *)description;
When the user shares to Facebook, their video is first uploaded to Kamcord. We will then use your settings to populate the corresponding fields on Facebook. Needless to say, this is a great way to advertise your game by putting links to your website or your game's page on the Apple App Store.
It's worth noting that every time we post to Facebook, we use the currently set values of these fields. Therefore, you may want to change the title, caption, and or description to match the results of the most recent gameplay. We recommend you do this so that the message looks more customized which should result in more clicks on the video.
Another function you need to set after you call `stopRecording` is:
+ (void) setLevel:(NSString *)level
score:(NSNumber *)score;
These values should be set per video. This metadata will be uploaded along with the video and be used to better organize videos for viewers.
### Developer Key, Secret, and Application Name
You must set your Kamcord developer key, secret, and app name using this function:
+ (void) setDeveloperKey:(NSString *)key
developerSecret:(NSString *)secret
appName:(NSString *)name;
We will give you a key and secret per game you build. We'll give you as many key/secret pairs you need, just don't tell them to anyone else.
## Examples
The `Examples` directory has some fully functional examples of how to use Kamcord in your application. You will recognize these as test apps that come bundled with Cocos2D. The following test apps have been ported over to `Kamcord`:
- ParticleTest
- RenderTextureTest
- RotateWorldTest
- SceneTest
- SpriteTest
#import <Kamcord/Kamcord.h>
Then do all the Kamcord initialization:
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
// Init the window
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// must be called before any other call to the director
[CCDirector setDirectorType:kCCDirectorTypeDisplayLink];
// before creating any layer, set the landscape mode
CCDirector *director = [CCDirector sharedDirector];
// set FPS at 60
[director setAnimationInterval:1.0/60];
// Display FPS: yes
[director setDisplayFPS:YES];
// Instantiate a KCGLView, which is a subclass with EAGLView with
// special recording functionality.
KCGLView * glView = [KCGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGB565
depthFormat:0];
// Kamcord uses UIKit for autorotation, which requires special logic to handle rotations.
window.rootViewController = [[KCViewController alloc] initWithNibName:nil bundle:nil];
window.rootViewController.view = glView;
// Tell Kamcord about the root view controller and the KCGLView
[Kamcord setParentViewController:window.rootViewController];
[Kamcord setOpenGLView:glView];
// Set the device orientation. Must use Kamcord, not CCDirector!
[Kamcord setDeviceOrientation:CCDeviceOrientationLandscapeLeft];
// Developer settings
[Kamcord setDeveloperKey:@"MY_DEVELOPER_KEY"
developerSecret:@"MY_DEVELOPER_SECRET"
appName:@"A Test App"];
// Social media settings
[Kamcord setYouTubeTitle:@"RenderTextureTest"
description:@"This is a Cocos2D test app that was recorded with Kamcord."
keywords:@"Cocos2D RenderTextureTest"];
[Kamcord setFacebookTitle:@"RenderTextureTest"
caption:@"Kamcord recording"
description:@"This is a Cocos2D test app that was recorded with Kamcord."];
// Set a background audio track we're going to loop over the recorded video.
[Kamcord setAudioResourceName:@"background"
extension:@"wav"];
// 2D projection
// [director setProjection:kCCDirectorProjection2D];
// Enables High Res mode (Retina Display) on iPhone 4 and maintains low res on all other devices
if( ! [director enableRetinaDisplay:YES] )
CCLOG(@"Retina Display Not supported");
// Not Kamcord specific, but don't forget to do this after
// all the Kamcord initialization is finished.
[window addSubview:glView];
[window makeKeyAndVisible];
// Default texture format for PNG/BMP/TIFF/JPEG/GIF images
// It can be RGBA8888, RGBA4444, RGB5_A1, RGB565
// You can change anytime.
[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];
CCScene *scene = [CCScene node];
[scene addChild: [nextAction() node]];
[director runWithScene: scene];
}
This code sets up the window's root view controller and gives it ownership of the `KCGLView`. It then starts recording a new video. The `Start Recording` and `Stop Recording` buttons in the app are hooked in as follows:
@interface KamcordRecording ()
@property (nonatomic, retain) KCAudio * sound1;
@property (nonatomic, retain) KCAudio * sound2;
@property (nonatomic, retain) AVAudioPlayer * audioPlayer1;
@property (nonatomic, retain) AVAudioPlayer * audioPlayer2;
@end
@implementation KamcordRecording
{
KCAudio * sound1_;
KCAudio * sound2_;
AVAudioPlayer * audioPlayer1_;
AVAudioPlayer * audioPlayer2_;
}
@synthesize sound1 = sound1_;
@synthesize sound2 = sound2_;
@synthesize audioPlayer1 = audioPlayer1_;
@synthesize audioPlayer2 = audioPlayer2_;
-(id) init
{
if( (self = [super init]) ) {
CGSize s = [[CCDirector sharedDirector] winSize];
// create a render texture, this is what we're going to draw into
target = [[CCRenderTexture renderTextureWithWidth:s.width height:s.height] retain];
[target setPosition:ccp(s.width/2, s.height/2)];
// It's possible to modify the RenderTexture blending function by
// [[target sprite] setBlendFunc:(ccBlendFunc) {GL_ONE, GL_ONE_MINUS_SRC_ALPHA}];
// note that the render texture is a CCNode, and contains a sprite of its texture for convience,
// so we can just parent it to the scene like any other CCNode
[self addChild:target z:-1];
// create a brush image to draw into the texture with
brush = [[CCSprite spriteWithFile:@"fire.png"] retain];
[brush setOpacity:20];
#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED
self.isTouchEnabled = YES;
#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED)
self.isMouseEnabled = YES;
lastLocation = CGPointMake( s.width/2, s.height/2);
#endif
[CCMenuItemFont setFont"Size:16];
CCMenuItem *item1 = [CCMenuItemFont itemFromString:@"Start Recording" target:self selector:@selector(startRecording:)];
CCMenuItem *item2 = [CCMenuItemFont itemFromString:@"Stop Recording" target:self selector:@selector(stopRecordingAndShowDialog:)];
CCMenuItem *item3 = [CCMenuItemFont itemFromString:@"Play Sound #1" target:self selector:@selector(playSound1:)];
CCMenuItem *item4 = [CCMenuItemFont itemFromString:@"Play Sound #2" target:self selector:@selector(playSound2:)];
CCMenuItem *item5 = [CCMenuItemFont itemFromString:@"Stop Sound #1" target:self selector:@selector(stopSound1:)];
CCMenuItem *item6 = [CCMenuItemFont itemFromString:@"Stop Sound #2" target:self selector:@selector(stopSound2:)];
CCMenuItem *item7 = [CCMenuItemFont itemFromString:@"Stop All Sounds" target:self selector:@selector(stopAllSounds:)];
CCMenu *menu = [CCMenu menuWithItems:item1, item2, item3, item4, item5, item6, item7, nil];
[self addChild:menu];
[menu alignItemsVertically];
[menu setPosition:ccp(s.width-80, s.height-90)];
}
return self;
}
-(void) startRecording:(id)sender
{
[Kamcord startRecording];
}
-(void) stopRecordingAndShowDialog:(id)sender
{
[Kamcord stopRecording];
[Kamcord showView];
}
-(void) playSound1:(id)sender
{
if (!self.audioPlayer1)
{
NSURL * url = [[NSBundle mainBundle] URLForResource:@"test8" withExtension:@"caf"];
self.audioPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
}
if ([self.audioPlayer1 play]) {
self.sound1 = [Kamcord playSound:@"test8.caf"];
}
}
-(void) playSound2:(id)sender
{
if (!self.audioPlayer2)
{
NSURL * url = [[NSBundle mainBundle] URLForResource:@"test3" withExtension:@"m4a"];
self.audioPlayer2 = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
}
if ([self.audioPlayer2 play]) {
self.sound2 = [Kamcord playSound:@"test3.m4a"];
}
}
-(void) stopSound1:(id)sender
{
[self.audioPlayer1 stop];
[self.sound1 stop];
}
-(void) stopSound2:(id)sender
{
[self.audioPlayer2 stop];
[self.sound2 stop];
}
-(void) stopAllSounds:(id)sender
{
[self.audioPlayer1 stop];
[self.audioPlayer2 stop];
[Kamcord stopAllSounds:NO];
}
For most games, you'll want to defer the calls to `startRecording` until appropriate (your user begins the actual level, etc.).
To highlight the handling of the application lifecycle, we've made additions to the following functions:
-(void) applicationWillResignActive:(UIApplication *)application
{
[[CCDirecto shraredDirector] pause];
[Kamcord pause];
}
-(void) applicationDidBecomeActive:(UIAplicpation *)applicaton
{i
[Kamcord resume];
[[CCDirector sharedDirector] resume];
}
That's all you have to do to manage the applicaton lifecycle. If no video is currently being recorded (i.e. `startRecording` has not been called), the calls to `pause` and `resume` do nothing.
To test this functionality, press `Start Recording`, play with the app, then close it by pressing the home button. Re-open the app, do some more actions, then press `Stop Recording`. When the Kamcord dialog appears, select `Replay Video`. It should show one seamless video of everything that's happened.
Note: in your game, you should defer calling `resume` until your user resumes gameplay. Calling it in `applicationDidBecomeActive:` like in this example will capture the pause screen of your game, which is probably not what you or your user wants.
## Implementing Custom UIs
As of 0.9.4, we've added support that lets game developers create their own UIs that interact with Kamcord. Please add a small `Powered by Kamcord` to your sharing view along the same veins as with the stock UI.
You can replay the most recent video in a `UIViewController` of your choice:
+ (void)presentVideoPlayerInViewController:(UIViewController *)parentViewController;
You can also share the most recent video by plugging into our API. There are two options.
### Option 1: Kamcord handles Facebook/Twitter/YouTube auth
The Kamcord stock UI is built on top of this option. If you don't want to deal with the hassle of doing Facebook/Twitter/YouTube auth, you should use the following API calls:
+ (void)showFacebookLoginView;
+ (void)authenticateTwitter;
+ (void)presentYouTubeLoginViewInViewController:(UIViewController *)parentViewController;
These functions pop up the Facebook login view, the Twitter auth alert view, and the YouTube login views, respectively. Once the user authenticates with these methods, Kamcord will store their credentials. You can verify the results of these authentication actions by implementing the following delegates inside the `KCShareDelegate` protocol:
- (void)facebookAuthFinishedWithSuccess:(BOOL)success;
- (void)twitterAuthFinishedWithSuccess:(BOOL)success;
- (void)youTubeAuthFinishedWithSuccess:(BOOL)success;
If the authentication was successfuly, the value of `success` will be `YES`, else `NO`. You can also check the status of authentication and logout the user with these calls:
+ (BOOL)facebookIsAuthenticated;
+ (BOOL)twitterIsAuthenticated;
+ (BOOL)youTubeIsAuthenticated;
+ (void)performFacebookLogout;
+ (void)performYouTubeLogout;
Once the user is authenticated to the relevant social networks, you can perform a share with those stored credentials with this API call:
+ (BOOL)shareVideoOnFacebook:(BOOL)shareFacebook
Twitter:(BOOL)shareTwitter
YouTube:(BOOL)shareYouTube
withMessage:(NSString *)message;
Simply set the values to YES for the networks you want to share to and give us the message the user wanted to share and we'll take care of the rest. One important note: this method returns `YES` if the request was accepted. If this returns `NO`, it means you tried to submit requests too quickly. More specifically, once you get one of the two following callbacks via the `KCShareDelegate` protocol:
- (void)shareStartedWithSuccess:(BOOL)success error:(KCShareStatus)error;
- (void)generalError:(KCShareStatus)error;
it will be safe to submit new share requests.
You will get callbacks about the status of the share via these callbacks from the `KCShareDelegate` protocol:
- (void)facebookShareStartedWithSuccess:(BOOL)success error:(KCShareStatus)error;
- (void)twitterShareStartedWithSuccess:(BOOL)success error:(KCShareStatus)error;
- (void)youTubeUploadStartedWithSuccess:(BOOL)success error:(KCShareStatus)error;
- (void)emailSentWithSuccess:(BOOL)success error:(KCShareStatus)error;
- (void)facebookShareFinishedWithSuccess:(BOOL)success error:(KCShareStatus)error;
- (void)twitterShareFinishedWithSuccess:(BOOL)success error:(KCShareStatus)error;
- (void)youTubeUploadFinishedWithSuccess:(BOOL)success error:(KCShareStatus)error;
The last three indicate that the requested message has been posted on the relevant social network.
You can also share with email using the following method:
+ (void)presentComposeEmailViewInViewController:(UIViewController *)parentViewController
withBody:(NSString *)bodyText;
for which the corresponding finishd callback is:
- (void)emailSentWithSuccess:(BOOL)success error:(KCShareStatus)error;
### Option 2: Kamcord only uploads the video and returns a shareable video URL and you handle the Facebook/Twitter/YouTube authentication and sharing
The API for case 2 is much simpler because you are responsible for all the sharing. Kamcord will only upload the video to Kamcord (and Youtube if requested), after which we will call back via the `KCShareDelegate` protocol to let you know the online URL of the video and thumbnail.
To ask Kamcord to upload the videos, call:
+ (BOOL)shareVideoWithMessage:(NSString *)message
withYouTubeAuth:(GTMOAuth2Authentication *)youTubeAuth
data:(NSDictionary *)data;
All videos will get uploaded to Kamcord. If you want to upload to YouTube also, pass in a valid auth object for the `youTubeAuth` argument. Otherwise, leave that argument `nil`. You can also pass in a dictionary of data that we will return to you once the video is ready to share. As with Option 1, this method returns a BOOL telling you whether or not the request was accepted. If the returned value was `NO`, you need to try again at a later time (typically on the order of seconds).
The callback you will receive is either `generalError:` or the following:
- (void)videoIsReadyToShare:(NSURL *)onlineVideoURL
thumbnail:(NSURL *)onlineThumbnailURL
message:(NSString *)message
data:(NSDictionary *)data
error:(NSError *)error;
You can use the video and thumbnail URLs to share to whichever social networks you like.
## Other Kamcord details
### Mixpanel
We collect analytics on user behavior. Specifically, we keep track of how often users replay videos, how often they share to social networks, which social networks they share to, etc. The information we track is purely aggregate statistics and **not** personally identifiable, so you can rest assured there are no privacy concerns for your end users.
### Amazon Simple Storage Service (S3)
Kamcord stores videos on Amazon S3, hence why we need `AWSiOSSDK.framework`.
## Troubleshooting
Below are various integration issues that some game developers have run into.
### Application crashes when you press [Done] on the main Kamcord view
Make sure that there is only **one** view controller in your application delegate, and barring some strange cases, that root view controller should be an instance of `KCViewController`. We've seen cases where people have had both a `RootViewController` and a `KCViewController` instantiated inside their app delegate. Removing the `RootViewController` solved the crash problems.
### Video uploads to kamcord.com don't succeed
Kamcord uploads videos in the background. This allows your user to get back to playing your game right away. Even after your app loses foreground, we still have 10 mins of time to upload. In the case that we can't finish it in those 10 minutes, we queue the job for next time the app regains foreground, and keep doing this until the video upload succeeds.
If you are testing Kamcord with XCode and press the `Stop` button too soon after you press `Share`, the video will most likely not have finished uploading. This is especially true for videos recorded with `TRAILER_VIDEO_RESOLUTION` since they are about 5x larger than those recorded with `SMART_VIDEO_RESOLUTION`. Videos also upload much more quickly on WiFi than 3G. To ensure the video upload succeeds, just leave the app running for a sufficient time and check the video on kamcord.com.
### Link error: Duplicate symbols in SBJsonWriter.o and SBJsonParser.o
This is a collision that results from `AWSiOSSDK.framework`. This framework is necessary in order for us to upload videos to our servers. Unfortunately, Amazon was careless and didn't rename the popular JSON library `SBJson` when they built their framework.
To remove these collisions, for every file `SBJson*.o` that complains of duplicate symbols, simply remove the corresopnding `SBJson*.m` file.
### armv6
Kamcord only supports i386 and armv7. If you need an armv6 build for some reason, shoot us an email.
## Contact Us
If you have any questions or comments, don't hesitate to email or call Matt at matt@kamcord.com (650.267.1051). We reply to every email!