android-emulator-webrtc 1.0.13 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Makefile CHANGED
@@ -43,12 +43,9 @@ endif
43
43
 
44
44
  MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
45
45
  CURRENT_DIR := $(abspath $(MAKEFILE_PATH)/..)
46
- QEMU_ROOT := $(abspath $(AOSP)/external/qemu)
47
- GRPC_ROOT := $(QEMU_ROOT)/objs/distribution/emulator/lib
48
-
49
46
  # Protobuf settings. If you are running this in the AOSP tree you will want to run ninja install first.
50
47
  PROTODIR := $(CURRENT_DIR)/src/proto/
51
- PROTOSRCDIR := $(if $(wildcard $(GRPC_ROOT)/*.proto), $(GRPC_ROOT), $(ANDROID_SDK_ROOT)/emulator/lib)
48
+ PROTOSRCDIR := $(CURRENT_DIR)/proto
52
49
  PROTO_SRC := $(wildcard $(PROTOSRCDIR)/*.proto)
53
50
  PROTO_OBJS := $(addprefix $(PROTODIR)/, $(notdir $(PROTO_SRC:.proto=_pb.js)))
54
51
  PROXY_OBJS := $(addprefix $(PROTODIR)/, $(notdir $(PROTO_SRC:.proto=_grpc_web_pb.js)))
@@ -61,12 +58,10 @@ LDFLAGS += -L$(PROTOLIB) -lprotoc -lprotobuf -lpthread -ldl
61
58
 
62
59
  .PHONY: build-release run-release develop stop
63
60
 
64
- all: build
61
+ all: check
65
62
 
66
63
  clean:
67
- rm -rf $(PROTODIR)
68
- rm -rf emulator
69
-
64
+ rm -rf $(PROTODIR)/*pb.js
70
65
 
71
66
  $(PROTODIR):
72
67
  @mkdir -p $(PROTODIR)
@@ -80,7 +75,7 @@ $(PROTODIR)/%_pb.js : $(PROTOSRCDIR)/%.proto $(PROTODIR) protoc-gen-grpc-web
80
75
  $(PROTOC) \
81
76
  -I/usr/local/include -I$(PROTODIR) -I$(PROTOSRCDIR) \
82
77
  --plugin=protoc-gen-grpc-web=$(CURRENT_DIR)/protoc-gen-grpc-web \
83
- --js_out=import_style=commonjs:$(PROTODIR) \
78
+ --js_out=import_style=commonjs,binary:$(PROTODIR) \
84
79
  --grpc-web_out=import_style=commonjs,mode=grpcwebtext:$(PROTODIR) \
85
80
  $<
86
81
  $(PREFIX_ESLINT) $@
@@ -90,19 +85,19 @@ $(PROTODIR)/%_pb.js : $(PROTOSRCDIR)/%.proto $(PROTODIR) protoc-gen-grpc-web
90
85
  $(PROTODIR)/%_grpc_web_pb.js : $(PROTODIR)/%_pb.js
91
86
  @test -f $@ && $(PREFIX_ESLINT) $@ || true
92
87
 
93
-
94
- protoc: $(PROTO_OBJS) $(PROXY_OBJS) $(PROTODIR)/emulator_web_client.js
88
+ protoc: $(PROTO_OBJS) $(PROXY_OBJS)
95
89
 
96
90
  protoc-gen-grpc-web: protoc-plugin/grpc_generator.o
97
91
  $(CXX) $^ $(LDFLAGS) -o $@
98
92
 
99
- deps: protoc system-check
93
+ deps: system-check protoc
100
94
  @npm install
101
95
 
102
-
103
96
  build: deps
104
97
  @npm run build
105
98
 
99
+ check: build
100
+ @npm run test
106
101
 
107
102
  system-check:
108
103
  ifneq ($(HAS_VALID_PROTOC),true)
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
-
5
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6
4
 
5
+ var _typeof = require("@babel/runtime/helpers/typeof");
6
+
7
7
  Object.defineProperty(exports, "__esModule", {
8
8
  value: true
9
9
  });
@@ -39,9 +39,13 @@ var Proto = _interopRequireWildcard(require("../../proto/emulator_controller_pb"
39
39
 
40
40
  var _emulator_web_client = require("../../proto/emulator_web_client");
41
41
 
42
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
43
+
44
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
45
+
42
46
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
43
47
 
44
- function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
48
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
45
49
 
46
50
  var PngView = (0, _event_handler["default"])(_simple_png_view["default"]);
47
51
  var RtcView = (0, _event_handler["default"])(_webrtc_view["default"]);
@@ -277,7 +277,7 @@ var JsepProtocol = /*#__PURE__*/function () {
277
277
  _this._handleSDP(_this.old_emu_patch.sdp);
278
278
  }
279
279
 
280
- if (_this.haveOffer) {
280
+ if (_this.old_emu_patch.haveOffer) {
281
281
  // We have a remote peer, add the candidates in.
282
282
  while (_this.old_emu_patch.candidates.length > 0) {
283
283
  _this._handleCandidate(_this.old_emu_patch.candidates.shift());
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
-
5
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6
4
 
5
+ var _typeof = require("@babel/runtime/helpers/typeof");
6
+
7
7
  Object.defineProperty(exports, "__esModule", {
8
8
  value: true
9
9
  });
@@ -31,9 +31,13 @@ var Proto = _interopRequireWildcard(require("../../../proto/emulator_controller_
31
31
 
32
32
  var _emulator_status = _interopRequireDefault(require("../net/emulator_status"));
33
33
 
34
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
35
+
36
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
37
+
34
38
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
35
39
 
36
- function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
40
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
37
41
 
38
42
  /**
39
43
  * A handler that extends a view to send key/mouse events to the emulator.
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
-
5
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6
4
 
5
+ var _typeof = require("@babel/runtime/helpers/typeof");
6
+
7
7
  Object.defineProperty(exports, "__esModule", {
8
8
  value: true
9
9
  });
@@ -31,9 +31,13 @@ var Proto = _interopRequireWildcard(require("../../../proto/emulator_controller_
31
31
 
32
32
  var _reactResizeObserver = _interopRequireDefault(require("react-resize-observer"));
33
33
 
34
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
35
+
36
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
37
+
34
38
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
35
39
 
36
- function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
40
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
37
41
 
38
42
  /**
39
43
  * A view on the emulator that is generated by streaming a series of screenshots.
@@ -60,7 +64,7 @@ var EmulatorPngView = /*#__PURE__*/function (_Component) {
60
64
  png: "",
61
65
  width: null,
62
66
  height: null,
63
- connect: "disconnected"
67
+ connect: "connecting"
64
68
  });
65
69
  (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "preventDragHandler", function (e) {
66
70
  e.preventDefault();
@@ -104,7 +108,7 @@ var EmulatorPngView = /*#__PURE__*/function (_Component) {
104
108
  var _this$state = this.state,
105
109
  width = _this$state.width,
106
110
  height = _this$state.height,
107
- connected = _this$state.connected;
111
+ connect = _this$state.connect;
108
112
 
109
113
  if (this.screen) {
110
114
  this.screen.cancel();
@@ -122,7 +126,7 @@ var EmulatorPngView = /*#__PURE__*/function (_Component) {
122
126
  emulator = _this$props.emulator,
123
127
  poll = _this$props.poll;
124
128
 
125
- if (poll && state !== "disconnected") {
129
+ if (poll && connect !== "disconnected") {
126
130
  emulator.getScreenshot(request, {}, function (err, response) {
127
131
  _this2.setState({
128
132
  connect: "connected"
@@ -131,22 +135,34 @@ var EmulatorPngView = /*#__PURE__*/function (_Component) {
131
135
 
132
136
  self.setState({
133
137
  png: "data:image/jpeg;base64," + response.getImage_asB64()
134
- });
135
-
136
- _this2.startStream(width, height);
138
+ }, _this2.startStream);
137
139
  });
138
140
  } else {
139
141
  this.screen = emulator.streamScreenshot(request);
140
142
  this.screen.on("data", function (response) {
141
- _this2.setState({
143
+ self.setState({
142
144
  connect: "connected"
143
145
  }, _this2.broadcastState); // Update the image with the one we just received.
144
146
 
145
-
146
147
  self.setState({
147
148
  png: "data:image/jpeg;base64," + response.getImage_asB64()
148
149
  });
149
150
  });
151
+ this.screen.on("error", function (e) {
152
+ console.warn("Screenshot stream broken", e);
153
+ var connect = _this2.state.connect;
154
+
155
+ if (connect === "connected") {
156
+ console.log("Reconnecting..");
157
+ self.setState({
158
+ connect: "connecting"
159
+ }, _this2.startStream);
160
+ } else {
161
+ self.setState({
162
+ connect: "disconnected"
163
+ }, _this2.broadcastState);
164
+ }
165
+ });
150
166
  }
151
167
  }
152
168
  }, {
@@ -165,7 +181,7 @@ var EmulatorPngView = /*#__PURE__*/function (_Component) {
165
181
  },
166
182
  onDragStart: this.preventDragHandler
167
183
  }, /*#__PURE__*/_react["default"].createElement(_reactResizeObserver["default"], {
168
- onResize: function onResize(rect) {
184
+ onReflow: function onReflow(rect) {
169
185
  self.setState({
170
186
  width: rect.width,
171
187
  height: rect.height
@@ -174,7 +190,10 @@ var EmulatorPngView = /*#__PURE__*/function (_Component) {
174
190
  }), /*#__PURE__*/_react["default"].createElement("img", {
175
191
  src: this.state.png,
176
192
  width: "100%",
177
- draggable: "false"
193
+ draggable: "false",
194
+ style: {
195
+ pointerEvents: "none"
196
+ }
178
197
  }));
179
198
  }
180
199
  }]);
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
-
5
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
6
4
 
5
+ var _typeof = require("@babel/runtime/helpers/typeof");
6
+
7
7
  Object.defineProperty(exports, "__esModule", {
8
8
  value: true
9
9
  });
@@ -27,9 +27,13 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
27
27
 
28
28
  var _react = _interopRequireWildcard(require("react"));
29
29
 
30
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
31
+
32
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
33
+
30
34
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
31
35
 
32
- function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
36
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
33
37
 
34
38
  /**
35
39
  * A view on the emulator that is using WebRTC. It will use the Jsep protocol over gRPC to
@@ -29,13 +29,13 @@ var _grpcWeb = require("grpc-web");
29
29
 
30
30
  var _events = require("events");
31
31
 
32
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
32
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
33
33
 
34
34
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
35
35
 
36
36
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
37
37
 
38
- function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
38
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
39
39
 
40
40
  var NopAuthenticator = function NopAuthenticator() {
41
41
  (0, _classCallCheck2["default"])(this, NopAuthenticator);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "android-emulator-webrtc",
3
- "version": "1.0.13",
3
+ "version": "1.0.16",
4
4
  "description": "Android Emulator WebRTC module",
5
5
  "scripts": {
6
6
  "build": "babel src -d dist",
@@ -271,7 +271,7 @@ export default class JsepProtocol {
271
271
  this._handleSDP(this.old_emu_patch.sdp);
272
272
  }
273
273
 
274
- if (this.haveOffer) {
274
+ if (this.old_emu_patch.haveOffer) {
275
275
  // We have a remote peer, add the candidates in.
276
276
  while (this.old_emu_patch.candidates.length > 0) {
277
277
  this._handleCandidate(this.old_emu_patch.candidates.shift());
@@ -51,7 +51,7 @@ export default class EmulatorPngView extends Component {
51
51
  png: "",
52
52
  width: null,
53
53
  height: null,
54
- connect: "disconnected",
54
+ connect: "connecting",
55
55
  };
56
56
 
57
57
  broadcastState() {
@@ -74,7 +74,7 @@ export default class EmulatorPngView extends Component {
74
74
  }
75
75
 
76
76
  startStream() {
77
- const { width, height, connected } = this.state;
77
+ const { width, height, connect } = this.state;
78
78
  if (this.screen) {
79
79
  this.screen.cancel();
80
80
  }
@@ -87,30 +87,42 @@ export default class EmulatorPngView extends Component {
87
87
 
88
88
  var self = this;
89
89
  const { emulator, poll } = this.props;
90
- if (poll && state !== "disconnected") {
90
+ if (poll && connect !== "disconnected") {
91
91
  emulator.getScreenshot(request, {}, (err, response) => {
92
92
  this.setState({ connect: "connected" }, this.broadcastState);
93
93
  // Update the image with the one we just received.
94
- self.setState({
95
- png: "data:image/jpeg;base64," + response.getImage_asB64(),
96
- });
97
- this.startStream(width, height);
94
+ self.setState(
95
+ {
96
+ png: "data:image/jpeg;base64," + response.getImage_asB64(),
97
+ },
98
+ this.startStream
99
+ );
98
100
  });
99
101
  } else {
100
102
  this.screen = emulator.streamScreenshot(request);
101
103
  this.screen.on("data", (response) => {
102
- this.setState({ connect: "connected" }, this.broadcastState);
104
+ self.setState({ connect: "connected" }, this.broadcastState);
103
105
  // Update the image with the one we just received.
104
106
  self.setState({
105
107
  png: "data:image/jpeg;base64," + response.getImage_asB64(),
106
108
  });
107
109
  });
110
+ this.screen.on("error", (e) => {
111
+ console.warn("Screenshot stream broken", e)
112
+ const { connect } = this.state;
113
+ if (connect === "connected") {
114
+ console.log("Reconnecting..");
115
+ self.setState({ connect: "connecting" }, this.startStream);
116
+ } else {
117
+ self.setState({ connect: "disconnected" }, this.broadcastState);
118
+ }
119
+ });
108
120
  }
109
121
  }
110
122
 
111
123
  preventDragHandler = (e) => {
112
124
  e.preventDefault();
113
- }
125
+ };
114
126
 
115
127
  render() {
116
128
  const { width } = this.props;
@@ -124,19 +136,23 @@ export default class EmulatorPngView extends Component {
124
136
  height: "100%",
125
137
  objectFit: "contain",
126
138
  objectPosition: "center",
127
-
128
139
  }}
129
140
  onDragStart={this.preventDragHandler}
130
141
  >
131
142
  <ResizeObserver
132
- onResize={(rect) => {
143
+ onReflow={(rect) => {
133
144
  self.setState(
134
145
  { width: rect.width, height: rect.height },
135
146
  self.startStream
136
147
  );
137
148
  }}
138
149
  />
139
- <img src={this.state.png} width="100%" draggable="false"/>
150
+ <img
151
+ src={this.state.png}
152
+ width="100%"
153
+ draggable="false"
154
+ style={{ pointerEvents: "none" }}
155
+ />
140
156
  </div>
141
157
  );
142
158
  }
@@ -17,9 +17,8 @@ import "@testing-library/jest-dom";
17
17
 
18
18
  import React from "react";
19
19
  import { render, fireEvent, screen } from "@testing-library/react";
20
+ import { fakeMouseEvent, fakeTouchEvent} from "./fake_events";
20
21
  import withMouseKeyHandler from "../src/components/emulator/views/event_handler";
21
- import * as Proto from "../src/proto/emulator_controller_pb";
22
- import * as Rtc from "../src/proto/rtc_service_pb";
23
22
 
24
23
  import JsepProtocol from "../src/components/emulator/net/jsep_protocol_driver";
25
24
  import {
@@ -43,33 +42,6 @@ class FakeEmulator extends React.Component {
43
42
  }
44
43
  }
45
44
 
46
- const fakeMouseEvent = (tp, x, y, props = {}) => {
47
- const event = new MouseEvent(tp, {
48
- bubbles: true,
49
- cancelable: true,
50
- ...props,
51
- });
52
-
53
- Object.defineProperty(event, "offsetX", { get: () => x });
54
- Object.defineProperty(event, "offsetY", { get: () => y });
55
- return event;
56
- };
57
-
58
- const fakeTouchEvent = (tp, x, y, force, props = {}) => {
59
- const event = new TouchEvent(tp, {
60
- bubbles: true,
61
- cancelable: true,
62
- ...props,
63
- });
64
-
65
- Object.defineProperty(event, "changedTouches", {
66
- get: () => [
67
- { clientX: x, clientY: y, radiusX: 4, radiusY: 4, force: force },
68
- ],
69
- });
70
- return event;
71
- };
72
-
73
45
  const TestView = withMouseKeyHandler(FakeEmulator);
74
46
  describe("The event handler using a real jsep serializer", () => {
75
47
 
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright 2021 The Android Open Source Project
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License")
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export function fakeMouseEvent(tp, x, y, props = {}) {
17
+ const event = new MouseEvent(tp, {
18
+ bubbles: true,
19
+ cancelable: true,
20
+ ...props,
21
+ });
22
+
23
+ Object.defineProperty(event, "offsetX", { get: () => x });
24
+ Object.defineProperty(event, "offsetY", { get: () => y });
25
+ return event;
26
+ }
27
+
28
+ export function fakeTouchEvent(tp, x, y, force, props = {}) {
29
+ const event = new TouchEvent(tp, {
30
+ bubbles: true,
31
+ cancelable: true,
32
+ ...props,
33
+ });
34
+
35
+ Object.defineProperty(event, "changedTouches", {
36
+ get: () => [
37
+ { clientX: x, clientY: y, radiusX: 4, radiusY: 4, force: force },
38
+ ],
39
+ });
40
+ return event;
41
+ }
42
+
43
+ export function resize(width, height) {
44
+ const resizeEvent = document.createEvent("Event");
45
+ resizeEvent.initEvent("resize", true, true);
46
+
47
+ global.window.innerWidth = width || global.window.innerWidth;
48
+ global.window.innerHeight = height || global.window.innerHeight;
49
+ global.window.dispatchEvent(resizeEvent);
50
+ }
@@ -18,7 +18,6 @@ import "regenerator-runtime/runtime"; // Somehow needed for jest to be happy wit
18
18
 
19
19
  import * as Proto from "../src/proto/emulator_controller_pb";
20
20
  import * as Rtc from "../src/proto/rtc_service_pb";
21
- import { render, waitFor, screen } from "@testing-library/react";
22
21
  import JsepProtocol from "../src/components/emulator/net/jsep_protocol_driver.js";
23
22
  import {
24
23
  RtcService,
@@ -0,0 +1,133 @@
1
+ /*
2
+ * Copyright 2021 The Android Open Source Project
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License")
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { jest } from "@jest/globals";
17
+ import "regenerator-runtime/runtime"; // Somehow needed for jest to be happy with ES6
18
+
19
+ import * as Proto from "../src/proto/emulator_controller_pb";
20
+ import React from "react";
21
+ import EmulatorPngView from "../src/components/emulator/views/simple_png_view";
22
+ import { resize } from "./fake_events";
23
+ import { render, screen } from "@testing-library/react";
24
+ import { EmulatorControllerService } from "../src/proto/emulator_web_client";
25
+ import { EventEmitter } from "events";
26
+
27
+ jest.mock("../src/proto/emulator_web_client");
28
+
29
+ const googleLogo =
30
+ "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";
31
+ const googleImage = new Proto.Image();
32
+ googleImage.setImage(Uint8Array.from(atob(googleLogo), (c) => c.charCodeAt(0)));
33
+
34
+ const stream = new EventEmitter();
35
+ describe("A simple png view", () => {
36
+ EmulatorControllerService.mockImplementation(() => {
37
+ let count = 0;
38
+ return {
39
+ getScreenshot: jest.fn((a, b, response) => {
40
+ count++;
41
+ if (count < 2) response(null, googleImage);
42
+ }),
43
+ streamScreenshot: jest.fn((request) => {
44
+ stream.removeAllListeners();
45
+ return {
46
+ on: (name, fn) => {
47
+ stream.on(name, fn);
48
+ },
49
+ cancel: jest.fn(),
50
+ };
51
+ }),
52
+ getStatus: jest.fn(() => {}),
53
+ };
54
+ });
55
+
56
+ beforeEach(()=>{
57
+ stream.removeAllListeners();
58
+ })
59
+
60
+ it("Get screenshot renders an image.", () => {
61
+ const emulatorServiceInstance = new EmulatorControllerService("http://foo");
62
+ const { container } = render(
63
+ <EmulatorPngView emulator={emulatorServiceInstance} poll={true} />
64
+ );
65
+ const pngView = container.querySelector("img");
66
+ expect(pngView.src).toBe("data:image/jpeg;base64," + googleLogo);
67
+ });
68
+
69
+ it("A resize triggers a new stream request.", () => {
70
+ const emulatorServiceInstance = new EmulatorControllerService("http://foo");
71
+ render(
72
+ <div data-testid="pngdiv">
73
+ <EmulatorPngView emulator={emulatorServiceInstance} />
74
+ </div>
75
+ );
76
+
77
+ // Initial stream request + resize event after mount.
78
+ expect(emulatorServiceInstance.streamScreenshot).toHaveBeenCalledTimes(2);
79
+
80
+ let pngview = screen.getByTestId("pngdiv").childNodes[0];
81
+ pngview.getBoundingClientRect = jest.fn();
82
+ pngview.getBoundingClientRect.mockReturnValueOnce({
83
+ width: 120,
84
+ height: 120,
85
+ top: 0,
86
+ left: 0,
87
+ bottom: 0,
88
+ right: 0,
89
+ });
90
+
91
+ // A resize triggers a new stream request.
92
+ resize(200, 300);
93
+ expect(emulatorServiceInstance.streamScreenshot).toHaveBeenCalledTimes(3);
94
+ });
95
+
96
+
97
+ it("Has a connected state after the first image arrives", () => {
98
+ const emulatorServiceInstance = new EmulatorControllerService("http://foo");
99
+ const changeState = jest.fn()
100
+ render(<EmulatorPngView emulator={emulatorServiceInstance} onStateChange={changeState}/>);
101
+ expect(changeState).toHaveBeenCalledWith("connecting");
102
+ stream.emit("data", googleImage);
103
+ expect(changeState).toHaveBeenCalledWith("connected");
104
+ });
105
+
106
+ it("Attempts to reconnect if the server disconnects", () => {
107
+ const emulatorServiceInstance = new EmulatorControllerService("http://foo");
108
+ render(<EmulatorPngView emulator={emulatorServiceInstance} />);
109
+ expect(emulatorServiceInstance.streamScreenshot).toHaveBeenCalledTimes(2);
110
+ stream.emit("data", googleImage);
111
+ stream.emit("error", "fda");
112
+ expect(emulatorServiceInstance.streamScreenshot).toHaveBeenCalledTimes(3);
113
+ });
114
+
115
+ it("Gives up on the second failure.", () => {
116
+ const emulatorServiceInstance = new EmulatorControllerService("http://foo");
117
+ const changeState = jest.fn()
118
+ render(<EmulatorPngView emulator={emulatorServiceInstance} onStateChange={changeState}/>);
119
+ expect(emulatorServiceInstance.streamScreenshot).toHaveBeenCalledTimes(2);
120
+
121
+ // Connect
122
+ stream.emit("data", googleImage);
123
+
124
+ // First break, we attempt a reconnect.
125
+ stream.emit("error", "fda");
126
+ expect(emulatorServiceInstance.streamScreenshot).toHaveBeenCalledTimes(3);
127
+ expect(changeState).not.toHaveBeenCalledWith("disconnected");
128
+
129
+ // We could not reconnect, so we fail.
130
+ stream.emit("error", "fda");
131
+ expect(changeState).toHaveBeenCalledWith("disconnected");
132
+ });
133
+ });