Nintendo SwitchのJoy-Conを利用した動画を撮影したときに、両手がふさがって停止ボタンが押せなかったので録画時間を指定できるUnityのエディタ拡張を作りました。

recorder

Recordボタンで録画を開始し、指定した時間を経過すると自動的に録画を停止してくれます。途中でStopボタンも押せます。

2017/11/07にスクリプト更新しました

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
using UnityEngine;
using UnityEditor;
using UnityEngine.Networking;
 
/// <summary>
/// HoloLensの録画を制御するエディタ拡張
/// </summary>
public class RecordController : EditorWindow
{
    /// <summary>
    /// デバイスポータルのURL(Wi-Fi接続時)
    /// </summary>
    private string url = "192.168.0.";
    // デバイスポータルのURL(USB接続時)
    //private string url = "127.0.0.1:10080";
 
    /// <summary>
    /// Wi-Fi接続時に必要なトークン
    /// </summary>
    private string csrfToken = "";
 
    /// <summary>
    /// CsrfTokenを取得したかどうか
    /// </summary>
    private bool gotToken = false;
 
    /// <summary>
    /// デバイスポータルのユーザ名
    /// </summary>
    private string usr = "";
 
    /// <summary>
    /// デバイスポータルのパスワード
    /// </summary>
    private string pass = "";
 
    /// <summary>
    /// ホログラムをキャプチャするかどうか
    /// </summary>
    private bool holo = true;
 
    /// <summary>
    /// カメラ画像をキャプチャするかどうか
    /// </summary>
    private bool pv = true;
 
    /// <summary>
    /// マイクを利用するかどうか
    /// </summary>
    private bool mic = true;
 
    /// <summary>
    /// アプリケーションのオーディオを録音するかどうか
    /// </summary>
    private bool loopback = true;
 
    /// <summary>
    /// ボタンラベル
    /// </summary>
    private string buttonLabel = "Record";
 
    /// <summary>
    /// 録画中かどうか
    /// </summary>
    private bool isRecording = false;
 
    /// <summary>
    /// 録画中は設定項目を編集不可にする
    /// </summary>
    private bool disabled = false;
 
    /// <summary>
    /// 録画時間
    /// </summary>
    private float recordTime = 5.0f;
 
    /// <summary>
    /// 録画終了時間
    /// </summary>
    private float endTime;
 
    /// <summary>
    /// 認証処理リクエスト
    /// </summary>
    private UnityWebRequest authReq;
 
    /// <summary>
    /// 録画開始リクエスト
    /// </summary>
    private UnityWebRequest startReq;
 
    /// <summary>
    /// 録画停止リクエスト
    /// </summary>
    private UnityWebRequest stopReq;
 
    /// <summary>
    /// Windowを表示する
    /// </summary>
    [MenuItem("Tool/HoloLens/RecordContoller")]
    public static void ShowWindow()
    {
        // すでにWindowが存在すればそのインスタンスを取得し、なければ生成する
        var window = EditorWindow.GetWindow(typeof(RecordController));
 
        // Windowのサイズを変更不可にする
        window.maxSize = window.minSize = new Vector2(400, 260);
 
        // Windowのタイトルを設定する
        window.titleContent = new GUIContent("Recorder");
    }
 
    /// <summary>
    /// オブジェクトがロードされたときに呼ばれる
    /// </summary>
    private void OnEnable()
    {
        if (gotToken == false)
        {
            // パスワードを入力し終えたら認証としたほうが好ましいが、
            // デバイスポータル情報をハードコードした場合こちらのほうが楽
            SendAuthRequest();
        }
    }
 
    /// <summary>
    /// キーボードフォーカスされたときに呼ばれる
    /// </summary>
    private void OnFocus()
    {
        if (gotToken == false)
        {
            SendAuthRequest();
        }
    }
 
    /// <summary>
    /// GUIを描画する
    /// </summary>
    private void OnGUI()
    {
        // 編集制御グループ
        EditorGUI.BeginDisabledGroup(disabled);
        {
            GUILayout.Label("DevicePortal Information", EditorStyles.boldLabel);
            EditorGUI.indentLevel++;
            {
                url = EditorGUILayout.TextField("Url", url);
                usr = EditorGUILayout.TextField("User Name", usr);
                pass = EditorGUILayout.PasswordField("Password", pass);
            }
            EditorGUI.indentLevel--;
            GUILayout.Space(10);
 
            GUILayout.Label("Record Settings", EditorStyles.boldLabel);
            EditorGUI.indentLevel++;
            {
                holo = EditorGUILayout.ToggleLeft("ホログラムをキャプチャする", holo);
                pv = EditorGUILayout.ToggleLeft("カメラ画像をキャプチャする", pv);
                mic = EditorGUILayout.ToggleLeft("マイクを利用する", mic);
                loopback = EditorGUILayout.ToggleLeft("アプリのオーディオを録音する", loopback);
                recordTime = EditorGUILayout.Slider("録画時間(秒)", recordTime, 1, 300);
            }
            EditorGUI.indentLevel--;
        }
        EditorGUI.EndDisabledGroup();
        GUILayout.Space(20);
 
        // ボタン処理
        if (GUILayout.Button(buttonLabel))
        {
            if (isRecording)
            {
                StopRecord();
            }
            else
            {
                StartRecord();
            }
        }
    }
 
    /// <summary>
    /// 録画を開始する
    /// </summary>
    private void StartRecord()
    {
        buttonLabel = "Stop";
        disabled = true;
 
        SendStartRecordRequest();
    }
 
    /// <summary>
    /// 録画を停止する
    /// </summary>
    private void StopRecord()
    {
        buttonLabel = "Record";
        disabled = false;
        isRecording = false;
        endTime = 0.0f;
        // プログレスバーを削除する
        EditorUtility.ClearProgressBar();
 
        SendStopRecordRequest();
    }
 
    /// <summary>
    /// 認証に必要なCSRF-Tokenを取得するリクエスト送信を行う
    /// </summary>
    /// <returns></returns>
    private void SendAuthRequest()
    {
        authReq = UnityWebRequest.Get("https://" + url);
        authReq.SetRequestHeader("Authorization", MakeAuthorizationString(usr, pass));
        authReq.SendWebRequest();
    }
 
    /// <summary>
    /// 録画開始のPOSTリクエスト送信を行う
    /// </summary>
    private void SendStartRecordRequest()
    {
        WWWForm form = new WWWForm();
        string api = "/api/holographic/mrc/video/control/start";
        string parameter =
            "?holo=" + holo.ToString().ToLower() +
            "&pv=" + pv.ToString().ToLower() +
            "&mic=" + mic.ToString().ToLower() +
            "&loopback=" + loopback.ToString().ToLower();
 
        startReq = UnityWebRequest.Post("http://" + url + api + parameter, form);
        startReq.SetRequestHeader("Authorization", MakeAuthorizationString(usr, pass));
        startReq.SetRequestHeader("X-CSRF-Token", csrfToken.Replace("CSRF-Token=", ""));
        startReq.SendWebRequest();
    }
 
    /// <summary>
    /// 録画停止のPOSTリクエスト送信を行う
    /// </summary>
    private void SendStopRecordRequest()
    {
        WWWForm form = new WWWForm();
        string api = "/api/holographic/mrc/video/control/stop";
        stopReq = UnityWebRequest.Post("http://" + url + api, form);
        stopReq.SetRequestHeader("Authorization", MakeAuthorizationString(usr, pass));
        stopReq.SetRequestHeader("x-csrf-token", csrfToken.Replace("CSRF-Token=", ""));
        stopReq.SendWebRequest();
    }
 
    /// <summary>
    /// ユーザ情報からベーシック認証の認証文字列を生成する
    /// </summary>
    /// <param name="username">ユーザ名</param>
    /// <param name="password">パスワード</param>
    /// <returns>認証文字列</returns>
    private string MakeAuthorizationString(string username, string password)
    {
        string auth = username + ":" + password;
        auth = System.Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(auth));
        auth = "Basic " + auth;
        return auth;
    }
 
    /// <summary>
    /// 毎フレーム処理を行う
    /// </summary>
    private void Update()
    {
        // エラー処理
        if (IsError()) return;
 
        // レスポンス受信後の処理
        HandleResponse();
 
        // Unityエディタが起動されてからの経過時間
        float timeSinceStartup = (float)EditorApplication.timeSinceStartup;
 
        if (isRecording)
        {
            // プログレスバー表示
            float leftTime = endTime - timeSinceStartup;
            float progress = (recordTime - leftTime) / recordTime;
            EditorUtility.DisplayProgressBar("録画中", "残り時間 " + leftTime.ToString("0.0") + "秒", progress);
 
            if (endTime <= timeSinceStartup)
            {
                // 設定した時間が経過したので録画停止する
                StopRecord();
            }
        }
    }
 
    /// <summary>
    /// エラーチェックを行う
    /// </summary>
    /// <returns>エラーの場合trueを返す</returns>
    private bool IsError()
    {
        if (IsRequestError(authReq)) return true;
        if (IsRequestError(startReq)) return true;
        if (IsRequestError(stopReq)) return true;
        return false;
    }
 
    /// <summary>
    /// リクエストのエラーチェックを行う
    /// </summary>
    /// <param name="request">リクエスト</param>
    /// <returns>エラーの場合trueを返す</returns>
    private bool IsRequestError(UnityWebRequest request)
    {
        if (request != null && request.error != null)
        {
            // 表示され続けるので一応コメント
            //Debug.Log("There was an error sending request to " + request.url + " : " + request.error + " " + request.isNetworkError);
            return true;
        }
        return false;
    }
 
    /// <summary>
    /// レスポンス処理を行う
    /// </summary>
    private void HandleResponse()
    {
        if (authReq != null && authReq.isDone)
        {
            Debug.Log("認証レスポンス受信");
            /*
            Dictionary<string, string> dic = authReq.GetResponseHeaders();
            foreach (KeyValuePair<string, string> pair in dic)
            {
                Debug.Log(pair.Key + " : " + pair.Value);
            }
            */
            csrfToken = authReq.GetResponseHeader("Set-Cookie");
            gotToken = true;
            authReq = null;
        }
 
        if (startReq != null && startReq.isDone)
        {
            Debug.Log("録画開始レスポンス受信");
            // 終了時間を設定
            endTime = (float)EditorApplication.timeSinceStartup + recordTime;
            isRecording = true;
            startReq = null;
        }
 
        if (stopReq != null && stopReq.isDone)
        {
            Debug.Log("録画終了レスポンス受信");
            stopReq = null;
        }
    }
 
} // class

以前、iPhoneから録画を制御するものも作っているので興味があればご確認ください。

[HoloLens]iPhoneから録画制御

また、HoloLensのREST APIに興味があればこちらもどうぞ。

[HoloLens]REST API

トラックバック

コメント

  1. […] iPhoneやエディタ拡張で録画制御すると停止操作を映さないで済むので結構気に入っていますが、試しにアプリ内に組み込んでみました。 […]

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です