Cocos2d-xにEveryplayを導入する


こんにちは@mettoboshiです。Cocos2d-x Advent Calendar 2015の5日目を担当させて頂きます。よろしくお願いします。今回は動画共有サービスであるEveryplayの導入と録画を実行するまでを書きたいと思います。

※今回作成したサンプルは以下にもあがっています
https://github.com/mettoboshi/everyplay-cocos2d-x

## 使用したライブラリのバージョンなど

* Cocos2d-x v3.9
* everyplay-ios-sdk v2.0.1
* everyplay-android-sdk v1.5.1

## Everyplayとは

Everyplay( https://everyplay.com/feed )は動画共有サービスです。
アプリにSDKを組み込むことで簡単にプレイ動画を録画したりシェアすることが可能です。
Everyplayは2014年にUnity社に買収されておりUnityで使うイメージがありますが、iOSやAndroid用のSDKが用意されているので、Cocos2d-xでも使用することが可能です。

他にも、Lobi REC SDK(https://lobi.co/recsdk) や Kamcord(https://www.kamcord.com/) などがありますが、Kamcordから今年の9月末にサポート終了の案内がでるなど動画共有サービスにおいても大きな変化があった一年になった気がします。
現状では、ユーザーが多いサービスを選択するのが今のところベターな気がしています。

## 実際の使用例

Braindots(http://translimit.co.jp/services/braindots.html) はCocos2d-xで作成されており、Everyplayを導入しています。

具体的には以下のように結果画面でリプレイのサムネイルを表示し、リプレイボタンを押した先で動画の再生／シェアができる部分がEveryplayを用いて実現されています。

![sample.png](https://qiita-image-store.s3.amazonaws.com/0/42925/9a71ce40-cdf8-3f24-6b1d-04604e8732e4.png)

このように、動画を録画するだけでなく、自分のプレイをわかりやすく表示したり、リプレイを見られるようにするという用途でもEveryplayは有効に使えることがわかると思います。

前置きはこれくらいにして、早速導入してみましょう。

## ダウンロード

以下のURLからSDKをダウンロードできます。

URL : https://developers.everyplay.com/download

![everyplay-download.png](https://qiita-image-store.s3.amazonaws.com/0/42925/6f43c708-a72c-ffa7-9c56-df4d18438729.png "everyplay-download.png")

「iOSカスタムエンジン(everyplay-ios-sdk-master.zip)」と「Androidカスタムエンジン(everyplay-android-sdk-master.zip)」をダウンロードします。

## インストール

ドキュメントは以下が参考になります。

EVERYPLAYを、IOSゲームへインテグレート
(https://developers.everyplay.com/documentation/Everyplay-integration-to-iOS-game.md)


ちなみに、Cocos2dに関するドキュメント(https://developers.everyplay.com/documentation/Everyplay-integration-to-Cocos2d-game.md) もあり、この中でcocos2d-xに関する記載もあるのですが、リポジトリが3年前だったのでそのままでは使えませんでした。(参考になる可能性はあります・・)

では気を取り直してインストールを続けましょう。

### iOSの場合

* Everyplay.frameworkとEveryplay.bundleの配置

ダウンロードしてきた、everyplay-ios-sdk-master.zipを解凍すると中にEveryplay.bundleとEveryplay.frameworkというファイルがあるので、これをプロジェクトに追加していきましょう。今回は、proj.ios_mac/配下に、Frameworks/Everyplayというフォルダを作成し、そこにコピーしました。

![iOSフォルダ構成.png](https://qiita-image-store.s3.amazonaws.com/0/42925/ef50b1fc-8182-db6e-f7ce-7b00e8ec522e.png "iOSフォルダ構成.png")

次に、XcodeのプロジェクトのFrameworks直下にドラッグ＆ドロップで上記2ファイルを追加します。

![xcodeフォルダ構成.png](https://qiita-image-store.s3.amazonaws.com/0/42925/1cc1f259-ca46-0501-82c7-66a3fab4bd27.png "xcodeフォルダ構成.png")

* フレームワークの追加

次は、Everyplayが使用するフレームワークを追加していきます。Cocos2d-xですでに使っているものもあるので、使用するのは以下の11個です。

AdSupport, AssetsLibrary, CoreImage, CoreMedia, CoreVideo, MessageUI, MobileCoreServices, Social, StoreKit, SystemConfiguration, Twitter

![ライブラリの追加.png](https://qiita-image-store.s3.amazonaws.com/0/42925/c6896f59-8ea6-e789-9c93-f61d005f324d.png "ライブラリの追加.png")

* コードの追加

今回は、AppController.hとAppController.mmに直接コードをdelegateを追加し、メソッドを追加しました。

``` AppController.h
#import <UIKit/UIKit.h>
#import <Everyplay/Everyplay.h> // add

@class RootViewController;

// add EveryplayDelegate
@interface AppController : NSObject <EveryplayDelegate, UIApplicationDelegate> {
UIWindow *window;
}
@property(nonatomic, readonly) RootViewController* viewController;

@end
```

``` AppController.mm
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

//・・・中略

  app->run();
  [Everyplay setClientId:@"f77b511414beb1ea7d97762cdc5849e2d2dd27d1" clientSecret:@"2260808cef426ac09037a6d69f4d073e67b380a9" redirectURI:@"https://m.everyplay.com/auth"];
  [Everyplay initWithDelegate:self andParentViewController:_viewController];
  [[[Everyplay sharedInstance] capture] autoRecordForSeconds:5 withDelay:1];
  return YES;
}

}
- (void)everyplayShown
{
  NSLog(@"everyplayshown");
  cocos2d::Director::getInstance()->pause();
}

- (void)everyplayHidden
{
  NSLog(@"everyplayHidden");
  cocos2d::Director::getInstance()->resume();
}

- (void)everyplayRecordingStopped
{
  NSLog(@"everyplayRecordingStopped");
  [[Everyplay sharedInstance] playLastRecording];
}
```

上記コードを追加し、実機で実行すると5秒後にEveryplayのリプレイ画面が表示されるはずです。

![everyplay-ios.jpg](https://qiita-image-store.s3.amazonaws.com/0/42925/b4d7d66f-c211-3f4f-614e-77a174d1cb3d.jpeg "everyplay-ios.jpg")

この画面の左下の再生ボタンを押下すると動画の再生、右上のボタンを押すとシェアができます。

また、今回はEveryplayの初期化でclientIdとclientSecretを設定していますが、これは公式のドキュメントに記載されているものなので、実際に使う場合はユーザ登録をしてアプリを作成しそのIDを使用しましょう。

では次にAndroidでも録画が実行できるようにしていきます。

### Androidの場合

* SDKのコピー

ダウンロードしてきた、everyplay-android-sdk-master.zipを解凍すると中にeveryplay-androidフォルダが作成されるので、proj.android配下にFrameworksフォルダを作成しコピーします。(everyplay-androidをEveryplayにリネームしていますが、これはiOSと合わせただけなので必須ではないです)

![Androidフォルダ構成.png](https://qiita-image-store.s3.amazonaws.com/0/42925/b91cd77f-d6e1-4e23-a823-49b400cc1810.png "Androidフォルダ構成.png")

* project.propertiesの編集

ここからはEclipseの作業になります。
先ほどコピーしたEveryplayのSDKをproject.propertiesに追加します。

``` project.properties
# add
android.library.reference.2=Frameworks/everyplay-android
```

jni/Application.mkを編集します。Everyplayではarmabiがないため、単純にコンパイルするとエラーになってしまうためです。

``` Application.mk
# add
APP_ABI := armeabi-v7a x86
```

proj.android/AndroidManifest.xmlにEveryplayで使用しているActivityを追加します。

30行目くらいのactivityが閉じたところの次に以下を追加します。

``` AndroidManifest.xml
       <activity android:name="com.everyplay.Everyplay.view.EveryplaySocialActivity"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.NoTitleBar"
                  android:windowSoftInputMode="adjustResize"
                  android:screenOrientation="sensor"
                  android:hardwareAccelerated="true" />

        <activity android:name="com.everyplay.Everyplay.view.EveryplayTextInputActivity"
                  android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.NoTitleBar"
                  android:windowSoftInputMode="stateVisible|adjustResize"
                  android:screenOrientation="sensor"
                  android:hardwareAccelerated="false" />

        <activity android:name="com.everyplay.Everyplay.view.videoplayer.EveryplayVideoPlayerActivity"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                  android:windowSoftInputMode="adjustResize"
                  android:screenOrientation="sensor"
                  android:hardwareAccelerated="true" />

        <activity android:name="com.everyplay.Everyplay.view.videoplayer.EveryplayVideoEditorActivity"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                  android:windowSoftInputMode="adjustResize"
                  android:screenOrientation="sensor"
                  android:hardwareAccelerated="true" />

        <activity android:name="com.everyplay.Everyplay.view.EveryplaySharingModalActivity"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@style/EveryplaySharingModal"
                  android:windowSoftInputMode="adjustResize"
                  android:screenOrientation="sensor"
                  android:fitsSystemWindows="true"
                  android:hardwareAccelerated="true" />

        <activity android:name="com.everyplay.Everyplay.view.browser.EveryplayBrowserActivity"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.NoTitleBar"
                  android:windowSoftInputMode="adjustResize"
                  android:screenOrientation="sensor"
                  android:hardwareAccelerated="false" />

        <activity android:name="com.everyplay.Everyplay.view.auth.EveryplayAuthActivity"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.NoTitleBar"
                  android:screenOrientation="sensor"
                  android:hardwareAccelerated="false"
                  android:windowSoftInputMode="adjustResize" />

        <activity android:name="com.everyplay.Everyplay.view.auth.EveryplayAddConnectionActivity"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.Dialog"
                  android:hardwareAccelerated="false"
                  android:screenOrientation="sensor"
                  android:windowSoftInputMode="adjustResize" />

        <activity android:name="com.everyplay.Everyplay.communication.socialnetworks.EveryplayFacebook"
                  android:configChanges="keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
                  android:theme="@android:style/Theme.Dialog"
                  android:hardwareAccelerated="false"
                  android:screenOrientation="sensor"
                  android:windowSoftInputMode="adjustResize" />

        <service  android:name="com.everyplay.Everyplay.communication.upload.EveryplayUploadService" />
```

一番最後のパーミッションの定義のところに以下を追加します。

``` AndroidManifest.xml
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```

これでAndroidのビルドは通るようになるはずです。
以下のコマンドでビルドがとおることを確認しておいてください。

``` sh
cocos run -p android
```

* 録画の実装

次はiOSと同じく自動的に録画をして結果を出せるようにしてみましょう。

今回はsrc/org/cocos2dx/cpp/AppActivityにまるっと書いてしまいましたが、AppActivityを汚すのは良くないので、ちゃんと作るときはEveryplayの機能は、EveryplayManager.javaのように別ファイルに切り出して、Cocos2d-xからJNIで呼び出すようにするとよいでしょう。

``` AppActivity
package org.cocos2dx.cpp;

import org.cocos2dx.lib.Cocos2dxActivity;

import com.everyplay.Everyplay.Everyplay;
import com.everyplay.Everyplay.IEveryplayListener;

import android.os.Bundle;

public class AppActivity extends Cocos2dxActivity implements IEveryplayListener {
	private static final String TAG = "EveryplayRecord";
	private static String CLIENT_ID = "f77b511414beb1ea7d97762cdc5849e2d2dd27d1";
	private static String CLIENT_SECRET = "2260808cef426ac09037a6d69f4d073e67b380a9";
	private static String REDIRECT_URI = "https://m.everyplay.com/auth";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setup();
	}

	public void setup() {
		Everyplay.configureEveryplay(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
		Everyplay.initEveryplay(this, this);

		new Thread(new Runnable(){
	        @Override
	        public void run()
	        {
	            try
	            {
	                Thread.sleep(20000);
	                Everyplay.startRecording();
	                Thread.sleep(10000);
	                Everyplay.stopRecording();
	                Everyplay.playLastRecording();
	            }
	            catch( InterruptedException e )
	            {
	                e.printStackTrace();
	            }
	        }
	    }).start();
	}
	
	@Override
	public void onEveryplayReadyForRecording(int enabled) {
	}

	@Override
	public void onEveryplayRecordingStarted() {
	}

	@Override
	public void onEveryplayRecordingStopped() {
	}

	protected void onPause() {
		super.onPause();
	}

	@Override
	protected void onResume() {
		super.onResume();
	}

	@Override
	public void onEveryplayHidden() {
	}

	@Override
	public void onEveryplayFaceCamSessionStarted() {
	}

	@Override
	public void onEveryplayFaceCamRecordingPermission(int granted) {
	}

	@Override
	public void onEveryplayShown() {
	}

	@Override
	public void onEveryplayFaceCamSessionStopped() {
	}

	@Override
	public void onEveryplayUploadDidStart(int videoId) {
	}

	@Override
	public void onEveryplayUploadDidProgress(int videoId, double progress) {
	}

	@Override
	public void onEveryplayUploadDidComplete(int videoId) {
	}

	@Override
	public void onEveryplayThumbnailReadyAtTextureId(int textureId,
			int portraitMode) {
	}

	@Override
	public void onEveryplayAccountDidChange() {
	}
}
```

さて、これで自動で録画してリプレイを表示してくれるようになったはずです。
セットアップと同時にSleepしながら実行しているメソッドが録画、停止、再生になります。

```
// 録画開始
Everyplay.startRecording();
// 録画停止
Everyplay.stopRecording();
// 最後に録画したリプレイを見る
Everyplay.playLastRecording();
```

あえてSleepを入れているのは、Androidの場合、setup後にonEveryplayReadyForRecordingのコールバックが返ってくるまで録画ができないのですが、このコールバックが返戻されるまでそれなりに時間がかかるからです。実際にアプリに組み込む際はこの辺も意識する必要があります。

![everyplay-android.jpg](https://qiita-image-store.s3.amazonaws.com/0/42925/24c61ea5-a2f5-7e61-f4ff-b120ac0463d0.jpeg "everyplay-android.jpg")

無事、Everyplayでリプレイが見られるようになりました。

## おわりに

今回はEveryplayの導入について紹介しました。
しかし、今日の実装ではアプリに組み込めないためこの後CocosからiOS/Android両方で使えるようにしたり、綺麗に分離したりする必要があります。

Everyplayには、録画・停止・再生以外にも一時停止/再開はもちろん、FaceCamで自分の顔や声を取りながらの録画、サムネイルを取得、メタデータを設定しておいて後でEveryplayのサービスAPIを使ってデータを取得できるようにすることなどまだ色々とできることがあります。
また、Everyplayはハマりどころがとてつもなく多く、録画できるようになってからも色々と苦労する点がありました。もし需要がありそうならその辺もまとめたいと思います。

ちなみにEveryplayで検索すると、さくっとプレイ動画が撮影できたみたいな記事がみつかりますが、これはだいたいUnityでやっている記事だと思います。Cocosでやると情報がめっちゃ少ないので結構辛い道のりになります。

Everyplayを使ってみたいCocosユーザの方にこの記事が少しでも足しになれば幸いです。(いるかな・・)

明日は、@kohashiさんの CocosStudioとカスタムクラス・カスタムアクションになるようです。CocosStudio使った開発に関する情報、楽しみですね。

では。