1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview
7 * Class implement the video frame recorder extension client.
8 */
9
10'use strict';
11
12/** @suppress {duplicate} */
13var remoting = remoting || {};
14
15/**
16 * @constructor
17 * @param {remoting.ClientPlugin} plugin
18 */
19remoting.VideoFrameRecorder = function(plugin) {
20  this.fileWriter_ = null;
21  this.isRecording_ = false;
22  this.plugin_ = plugin;
23};
24
25/**
26 * Starts or stops video recording.
27 */
28remoting.VideoFrameRecorder.prototype.startStopRecording = function() {
29  var data = {};
30  if (this.isRecording_) {
31    this.isRecording_ = false;
32    data = { type: 'stop' }
33
34    chrome.fileSystem.chooseEntry(
35        {type: 'saveFile', suggestedName: 'videoRecording.pb'},
36        this.onFileChosen_.bind(this));
37  } else {
38    this.isRecording_ = true;
39    data = { type: 'start' }
40  }
41  this.plugin_.sendClientMessage('video-recorder', JSON.stringify(data));
42}
43
44/**
45 * Returns true if the session is currently being recorded.
46 * @return {boolean}
47 */
48remoting.VideoFrameRecorder.prototype.isRecording = function() {
49  return this.isRecording_;
50}
51
52/**
53 * Handles 'video-recorder' extension messages and returns true. Returns
54 * false for all other message types.
55 * @param {string} type Type of extension message.
56 * @param {string} data Content of the extension message.
57 * @return {boolean}
58 */
59remoting.VideoFrameRecorder.prototype.handleMessage =
60    function(type, data) {
61  if (type != 'video-recorder') {
62    return false;
63  }
64
65  var message = getJsonObjectFromString(data);
66  var messageType = getStringAttr(message, 'type');
67  var messageData = getStringAttr(message, 'data');
68
69  if (messageType == 'next-frame-reply') {
70    if (!this.fileWriter_) {
71      console.log("Received frame but have no writer");
72      return true;
73    }
74    if (!messageData) {
75      console.log("Finished receiving frames");
76      this.fileWriter_ = null;
77      return true;
78    }
79
80    console.log("Received frame");
81    /* jscompile gets confused if you refer to this as just atob(). */
82    var videoPacketString = /** @type {string?} */ window.atob(messageData);
83
84    console.log("Converted from Base64 - length:" + videoPacketString.length);
85    var byteArrays = [];
86
87    for (var offset = 0; offset < videoPacketString.length; offset += 512) {
88        var slice = videoPacketString.slice(offset, offset + 512);
89        var byteNumbers = new Array(slice.length);
90        for (var i = 0; i < slice.length; i++) {
91            byteNumbers[i] = slice.charCodeAt(i);
92        }
93        var byteArray = new Uint8Array(byteNumbers);
94        byteArrays.push(byteArray);
95    }
96
97    console.log("Writing frame");
98    videoPacketString = null;
99    /**
100     * Our current compiler toolchain only understands the old (deprecated)
101     * Blob constructor, which does not accept any parameters.
102     * TODO(wez): Remove this when compiler is updated (see crbug.com/405298).
103     * @suppress {checkTypes}
104     * @param {Array} parts
105     * @return {Blob}
106     */
107    var makeBlob = function(parts) {
108      return new Blob(parts);
109    }
110    var videoPacketBlob = makeBlob(byteArrays);
111    byteArrays = null;
112
113    this.fileWriter_.write(videoPacketBlob);
114
115    return true;
116  }
117
118  console.log("Unrecognized message: " + messageType);
119  return true;
120}
121
122/** @param {FileEntry} fileEntry */
123remoting.VideoFrameRecorder.prototype.onFileChosen_ = function(fileEntry) {
124  if (!fileEntry) {
125    console.log("Cancelled save of video frames.");
126  } else {
127    /** @type {function(string):void} */
128    chrome.fileSystem.getDisplayPath(fileEntry, function(path) {
129      console.log("Saving video frames to:" + path);
130    });
131    fileEntry.createWriter(this.onFileWriter_.bind(this));
132  }
133}
134
135/** @param {FileWriter} fileWriter */
136remoting.VideoFrameRecorder.prototype.onFileWriter_ = function(fileWriter) {
137  console.log("Obtained FileWriter for video frame write");
138  fileWriter.onwriteend = this.onWriteComplete_.bind(this);
139  this.fileWriter_ = fileWriter;
140  this.fetchNextFrame_();
141}
142
143remoting.VideoFrameRecorder.prototype.onWriteComplete_ = function(e) {
144  console.log("Video frame write complete");
145  this.fetchNextFrame_();
146}
147
148remoting.VideoFrameRecorder.prototype.fetchNextFrame_ = function() {
149  console.log("Request next video frame");
150  var data = { type: 'next-frame' }
151  this.plugin_.sendClientMessage('video-recorder', JSON.stringify(data));
152}
153