1 // Copyright (c) 2012 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.
7 * Class that wraps low-level details of interacting with the client plugin.
9 * This abstracts a <embed> element and controls the plugin which does
10 * the actual remoting work. It also handles differences between
11 * client plugins versions when it is necessary.
16 /** @suppress {duplicate} */
17 var remoting
= remoting
|| {};
20 * @param {Element} container The container for the embed element.
21 * @param {function(string, string):boolean} onExtensionMessage The handler for
22 * protocol extension messages. Returns true if a message is recognized;
25 * @implements {remoting.ClientPlugin}
27 remoting
.ClientPluginImpl = function(container
, onExtensionMessage
) {
28 this.plugin_
= remoting
.ClientPluginImpl
.createPluginElement_();
29 this.plugin_
.id
= 'session-client-plugin';
30 container
.appendChild(this.plugin_
);
32 this.onExtensionMessage_
= onExtensionMessage
;
35 this.desktopWidth_
= 0;
37 this.desktopHeight_
= 0;
39 this.desktopXDpi_
= 96;
41 this.desktopYDpi_
= 96;
44 * @param {string} iq The Iq stanza received from the host.
47 this.onOutgoingIqHandler_ = function (iq
) {};
49 * @param {string} message Log message.
52 this.onDebugMessageHandler_ = function (message
) {};
54 * @param {number} state The connection state.
55 * @param {number} error The error code, if any.
58 this.onConnectionStatusUpdateHandler_ = function(state
, error
) {};
60 * @param {boolean} ready Connection ready state.
63 this.onConnectionReadyHandler_ = function(ready
) {};
66 * @param {string} tokenUrl Token-request URL, received from the host.
67 * @param {string} hostPublicKey Public key for the host.
68 * @param {string} scope OAuth scope to request the token for.
71 this.fetchThirdPartyTokenHandler_ = function(
72 tokenUrl
, hostPublicKey
, scope
) {};
74 this.onDesktopSizeUpdateHandler_ = function () {};
76 * @param {!Array.<string>} capabilities The negotiated capabilities.
79 this.onSetCapabilitiesHandler_ = function (capabilities
) {};
81 this.fetchPinHandler_ = function (supportsPairing
) {};
83 * @param {string} data Remote gnubbyd data.
86 this.onGnubbyAuthHandler_ = function(data
) {};
89 * @param {number} hotspotX
90 * @param {number} hotspotY
93 this.updateMouseCursorImage_ = function(url
, hotspotX
, hotspotY
) {};
96 * @param {string} data Remote cast extension message.
99 this.onCastExtensionHandler_ = function(data
) {};
102 * @type {remoting.MediaSourceRenderer}
105 this.mediaSourceRenderer_
= null;
111 this.pluginApiVersion_
= -1;
113 * @type {Array.<string>}
116 this.pluginApiFeatures_
= [];
121 this.pluginApiMinVersion_
= -1;
123 * @type {!Array.<string>}
126 this.capabilities_
= [];
131 this.helloReceived_
= false;
133 * @type {function(boolean)|null}
136 this.onInitializedCallback_
= null;
138 * @type {function(string, string):void}
141 this.onPairingComplete_ = function(clientId
, sharedSecret
) {};
143 * @type {remoting.ClientSession.PerfStats}
146 this.perfStats_
= new remoting
.ClientSession
.PerfStats();
148 /** @type {remoting.ClientPluginImpl} */
150 /** @param {Event} event Message event from the plugin. */
151 this.plugin_
.addEventListener('message', function(event
) {
152 that
.handleMessage_(event
.data
);
155 if (remoting
.settings
.CLIENT_PLUGIN_TYPE
== 'native') {
156 window
.setTimeout(this.showPluginForClickToPlay_
.bind(this), 500);
161 * Creates plugin element without adding it to a container.
163 * @return {remoting.ViewerPlugin} Plugin element
165 remoting
.ClientPluginImpl
.createPluginElement_ = function() {
166 var plugin
= /** @type {remoting.ViewerPlugin} */
167 document
.createElement('embed');
168 if (remoting
.settings
.CLIENT_PLUGIN_TYPE
== 'pnacl') {
169 plugin
.src
= 'remoting_client_pnacl.nmf';
170 plugin
.type
= 'application/x-pnacl';
171 } else if (remoting
.settings
.CLIENT_PLUGIN_TYPE
== 'nacl') {
172 plugin
.src
= 'remoting_client_nacl.nmf';
173 plugin
.type
= 'application/x-nacl';
175 plugin
.src
= 'about://none';
176 plugin
.type
= 'application/vnd.chromium.remoting-viewer';
180 plugin
.tabIndex
= 0; // Required, otherwise focus() doesn't work.
185 * Chromoting session API version (for this javascript).
186 * This is compared with the plugin API version to verify that they are
192 remoting
.ClientPluginImpl
.prototype.API_VERSION_
= 6;
195 * The oldest API version that we support.
196 * This will differ from the |API_VERSION_| if we maintain backward
197 * compatibility with older API versions.
202 remoting
.ClientPluginImpl
.prototype.API_MIN_VERSION_
= 5;
205 * @param {function(string):void} handler
207 remoting
.ClientPluginImpl
.prototype.setOnOutgoingIqHandler = function(handler
) {
208 this.onOutgoingIqHandler_
= handler
;
212 * @param {function(string):void} handler
214 remoting
.ClientPluginImpl
.prototype.setOnDebugMessageHandler
=
216 this.onDebugMessageHandler_
= handler
;
220 * @param {function(number, number):void} handler
222 remoting
.ClientPluginImpl
.prototype.setConnectionStatusUpdateHandler
=
224 this.onConnectionStatusUpdateHandler_
= handler
;
228 * @param {function(boolean):void} handler
230 remoting
.ClientPluginImpl
.prototype.setConnectionReadyHandler
=
232 this.onConnectionReadyHandler_
= handler
;
236 * @param {function():void} handler
238 remoting
.ClientPluginImpl
.prototype.setDesktopSizeUpdateHandler
=
240 this.onDesktopSizeUpdateHandler_
= handler
;
244 * @param {function(!Array.<string>):void} handler
246 remoting
.ClientPluginImpl
.prototype.setCapabilitiesHandler = function(handler
) {
247 this.onSetCapabilitiesHandler_
= handler
;
251 * @param {function(string):void} handler
253 remoting
.ClientPluginImpl
.prototype.setGnubbyAuthHandler = function(handler
) {
254 this.onGnubbyAuthHandler_
= handler
;
258 * @param {function(string):void} handler
260 remoting
.ClientPluginImpl
.prototype.setCastExtensionHandler
=
262 this.onCastExtensionHandler_
= handler
;
266 * @param {function(string, number, number):void} handler
268 remoting
.ClientPluginImpl
.prototype.setMouseCursorHandler = function(handler
) {
269 this.updateMouseCursorImage_
= handler
;
273 * @param {function(string, string, string):void} handler
275 remoting
.ClientPluginImpl
.prototype.setFetchThirdPartyTokenHandler
=
277 this.fetchThirdPartyTokenHandler_
= handler
;
281 * @param {function(boolean):void} handler
283 remoting
.ClientPluginImpl
.prototype.setFetchPinHandler = function(handler
) {
284 this.fetchPinHandler_
= handler
;
288 * @param {string|{method:string, data:Object.<string,*>}}
289 * rawMessage Message from the plugin.
292 remoting
.ClientPluginImpl
.prototype.handleMessage_ = function(rawMessage
) {
294 /** @type {{method:string, data:Object.<string,*>}} */
295 ((typeof(rawMessage
) == 'string') ? jsonParseSafe(rawMessage
)
297 if (!message
|| !('method' in message
) || !('data' in message
)) {
298 console
.error('Received invalid message from the plugin:', rawMessage
);
303 this.handleMessageMethod_(message
);
305 console
.error(/** @type {*} */ (e
));
310 * @param {{method:string, data:Object.<string,*>}}
311 * message Parsed message from the plugin.
314 remoting
.ClientPluginImpl
.prototype.handleMessageMethod_ = function(message
) {
316 * Splits a string into a list of words delimited by spaces.
317 * @param {string} str String that should be split.
318 * @return {!Array.<string>} List of words.
320 var tokenize = function(str
) {
321 /** @type {Array.<string>} */
322 var tokens
= str
.match(/\S+/g);
323 return tokens
? tokens
: [];
326 if (message
.method
== 'hello') {
327 // Resize in case we had to enlarge it to support click-to-play.
328 this.hidePluginForClickToPlay_();
329 this.pluginApiVersion_
= getNumberAttr(message
.data
, 'apiVersion');
330 this.pluginApiMinVersion_
= getNumberAttr(message
.data
, 'apiMinVersion');
332 if (this.pluginApiVersion_
>= 7) {
333 this.pluginApiFeatures_
=
334 tokenize(getStringAttr(message
.data
, 'apiFeatures'));
336 // Negotiate capabilities.
338 /** @type {!Array.<string>} */
339 var requestedCapabilities
= [];
340 if ('requestedCapabilities' in message
.data
) {
341 requestedCapabilities
=
342 tokenize(getStringAttr(message
.data
, 'requestedCapabilities'));
345 /** @type {!Array.<string>} */
346 var supportedCapabilities
= [];
347 if ('supportedCapabilities' in message
.data
) {
348 supportedCapabilities
=
349 tokenize(getStringAttr(message
.data
, 'supportedCapabilities'));
352 // At the moment the webapp does not recognize any of
353 // 'requestedCapabilities' capabilities (so they all should be disabled)
354 // and do not care about any of 'supportedCapabilities' capabilities (so
355 // they all can be enabled).
356 this.capabilities_
= supportedCapabilities
;
358 // Let the host know that the webapp can be requested to always send
359 // the client's dimensions.
360 this.capabilities_
.push(
361 remoting
.ClientSession
.Capability
.SEND_INITIAL_RESOLUTION
);
363 // Let the host know that we're interested in knowing whether or not
364 // it rate-limits desktop-resize requests.
365 this.capabilities_
.push(
366 remoting
.ClientSession
.Capability
.RATE_LIMIT_RESIZE_REQUESTS
);
368 // Let the host know that we can use the video framerecording extension.
369 this.capabilities_
.push(
370 remoting
.ClientSession
.Capability
.VIDEO_RECORDER
);
372 // Let the host know that we can support casting of the screen.
373 // TODO(aiguha): Add this capability based on a gyp/command-line flag,
374 // rather than by default.
375 this.capabilities_
.push(
376 remoting
.ClientSession
.Capability
.CAST
);
378 } else if (this.pluginApiVersion_
>= 6) {
379 this.pluginApiFeatures_
= ['highQualityScaling', 'injectKeyEvent'];
381 this.pluginApiFeatures_
= ['highQualityScaling'];
383 this.helloReceived_
= true;
384 if (this.onInitializedCallback_
!= null) {
385 this.onInitializedCallback_(true);
386 this.onInitializedCallback_
= null;
389 } else if (message
.method
== 'sendOutgoingIq') {
390 this.onOutgoingIqHandler_(getStringAttr(message
.data
, 'iq'));
392 } else if (message
.method
== 'logDebugMessage') {
393 this.onDebugMessageHandler_(getStringAttr(message
.data
, 'message'));
395 } else if (message
.method
== 'onConnectionStatus') {
396 var state
= remoting
.ClientSession
.State
.fromString(
397 getStringAttr(message
.data
, 'state'))
398 var error
= remoting
.ClientSession
.ConnectionError
.fromString(
399 getStringAttr(message
.data
, 'error'));
400 this.onConnectionStatusUpdateHandler_(state
, error
);
402 } else if (message
.method
== 'onDesktopSize') {
403 this.desktopWidth_
= getNumberAttr(message
.data
, 'width');
404 this.desktopHeight_
= getNumberAttr(message
.data
, 'height');
405 this.desktopXDpi_
= getNumberAttr(message
.data
, 'x_dpi', 96);
406 this.desktopYDpi_
= getNumberAttr(message
.data
, 'y_dpi', 96);
407 this.onDesktopSizeUpdateHandler_();
409 } else if (message
.method
== 'onPerfStats') {
410 // Return value is ignored. These calls will throw an error if the value
412 getNumberAttr(message
.data
, 'videoBandwidth');
413 getNumberAttr(message
.data
, 'videoFrameRate');
414 getNumberAttr(message
.data
, 'captureLatency');
415 getNumberAttr(message
.data
, 'encodeLatency');
416 getNumberAttr(message
.data
, 'decodeLatency');
417 getNumberAttr(message
.data
, 'renderLatency');
418 getNumberAttr(message
.data
, 'roundtripLatency');
420 /** @type {remoting.ClientSession.PerfStats} */ message
.data
;
422 } else if (message
.method
== 'injectClipboardItem') {
423 var mimetype
= getStringAttr(message
.data
, 'mimeType');
424 var item
= getStringAttr(message
.data
, 'item');
425 if (remoting
.clipboard
) {
426 remoting
.clipboard
.fromHost(mimetype
, item
);
429 } else if (message
.method
== 'onFirstFrameReceived') {
430 if (remoting
.clientSession
) {
431 remoting
.clientSession
.onFirstFrameReceived();
434 } else if (message
.method
== 'onConnectionReady') {
435 var ready
= getBooleanAttr(message
.data
, 'ready');
436 this.onConnectionReadyHandler_(ready
);
438 } else if (message
.method
== 'fetchPin') {
439 // The pairingSupported value in the dictionary indicates whether both
440 // client and host support pairing. If the client doesn't support pairing,
441 // then the value won't be there at all, so give it a default of false.
442 var pairingSupported
= getBooleanAttr(message
.data
, 'pairingSupported',
444 this.fetchPinHandler_(pairingSupported
);
446 } else if (message
.method
== 'setCapabilities') {
447 /** @type {!Array.<string>} */
448 var capabilities
= tokenize(getStringAttr(message
.data
, 'capabilities'));
449 this.onSetCapabilitiesHandler_(capabilities
);
451 } else if (message
.method
== 'fetchThirdPartyToken') {
452 var tokenUrl
= getStringAttr(message
.data
, 'tokenUrl');
453 var hostPublicKey
= getStringAttr(message
.data
, 'hostPublicKey');
454 var scope
= getStringAttr(message
.data
, 'scope');
455 this.fetchThirdPartyTokenHandler_(tokenUrl
, hostPublicKey
, scope
);
457 } else if (message
.method
== 'pairingResponse') {
458 var clientId
= getStringAttr(message
.data
, 'clientId');
459 var sharedSecret
= getStringAttr(message
.data
, 'sharedSecret');
460 this.onPairingComplete_(clientId
, sharedSecret
);
462 } else if (message
.method
== 'extensionMessage') {
463 var extMsgType
= getStringAttr(message
.data
, 'type');
464 var extMsgData
= getStringAttr(message
.data
, 'data');
465 switch (extMsgType
) {
467 this.onGnubbyAuthHandler_(extMsgData
);
469 case 'test-echo-reply':
470 console
.log('Got echo reply: ' + extMsgData
);
473 this.onCastExtensionHandler_(extMsgData
);
476 this.onExtensionMessage_(extMsgType
, extMsgData
);
480 } else if (message
.method
== 'mediaSourceReset') {
481 if (!this.mediaSourceRenderer_
) {
482 console
.error('Unexpected mediaSourceReset.');
485 this.mediaSourceRenderer_
.reset(getStringAttr(message
.data
, 'format'))
487 } else if (message
.method
== 'mediaSourceData') {
488 if (!(message
.data
['buffer'] instanceof ArrayBuffer
)) {
489 console
.error('Invalid mediaSourceData message:', message
.data
);
492 if (!this.mediaSourceRenderer_
) {
493 console
.error('Unexpected mediaSourceData.');
496 // keyframe flag may be absent from the message.
497 var keyframe
= !!message
.data
['keyframe'];
498 this.mediaSourceRenderer_
.onIncomingData(
499 (/** @type {ArrayBuffer} */ message
.data
['buffer']), keyframe
);
501 } else if (message
.method
== 'unsetCursorShape') {
502 this.updateMouseCursorImage_('', 0, 0);
504 } else if (message
.method
== 'setCursorShape') {
505 var width
= getNumberAttr(message
.data
, 'width');
506 var height
= getNumberAttr(message
.data
, 'height');
507 var hotspotX
= getNumberAttr(message
.data
, 'hotspotX');
508 var hotspotY
= getNumberAttr(message
.data
, 'hotspotY');
509 var srcArrayBuffer
= getObjectAttr(message
.data
, 'data');
512 /** @type {HTMLCanvasElement} */ (document
.createElement('canvas'));
513 canvas
.width
= width
;
514 canvas
.height
= height
;
517 /** @type {CanvasRenderingContext2D} */ (canvas
.getContext('2d'));
518 var imageData
= context
.getImageData(0, 0, width
, height
);
519 base
.debug
.assert(srcArrayBuffer
instanceof ArrayBuffer
);
520 var src
= new Uint8Array(/** @type {ArrayBuffer} */(srcArrayBuffer
));
521 var dest
= imageData
.data
;
522 for (var i
= 0; i
< /** @type {number} */(dest
.length
); i
+= 4) {
523 dest
[i
] = src
[i
+ 2];
524 dest
[i
+ 1] = src
[i
+ 1];
525 dest
[i
+ 2] = src
[i
];
526 dest
[i
+ 3] = src
[i
+ 3];
529 context
.putImageData(imageData
, 0, 0);
530 this.updateMouseCursorImage_(canvas
.toDataURL(), hotspotX
, hotspotY
);
535 * Deletes the plugin.
537 remoting
.ClientPluginImpl
.prototype.dispose = function() {
539 this.plugin_
.parentNode
.removeChild(this.plugin_
);
545 * @return {HTMLEmbedElement} HTML element that corresponds to the plugin.
547 remoting
.ClientPluginImpl
.prototype.element = function() {
552 * @param {function(boolean): void} onDone
554 remoting
.ClientPluginImpl
.prototype.initialize = function(onDone
) {
555 if (this.helloReceived_
) {
558 this.onInitializedCallback_
= onDone
;
563 * @return {boolean} True if the plugin and web-app versions are compatible.
565 remoting
.ClientPluginImpl
.prototype.isSupportedVersion = function() {
566 if (!this.helloReceived_
) {
568 "isSupportedVersion() is called before the plugin is initialized.");
571 return this.API_VERSION_
>= this.pluginApiMinVersion_
&&
572 this.pluginApiVersion_
>= this.API_MIN_VERSION_
;
576 * @param {remoting.ClientPlugin.Feature} feature The feature to test for.
577 * @return {boolean} True if the plugin supports the named feature.
579 remoting
.ClientPluginImpl
.prototype.hasFeature = function(feature
) {
580 if (!this.helloReceived_
) {
582 "hasFeature() is called before the plugin is initialized.");
585 return this.pluginApiFeatures_
.indexOf(feature
) > -1;
589 * @return {boolean} True if the plugin supports the injectKeyEvent API.
591 remoting
.ClientPluginImpl
.prototype.isInjectKeyEventSupported = function() {
592 return this.pluginApiVersion_
>= 6;
596 * @param {string} iq Incoming IQ stanza.
598 remoting
.ClientPluginImpl
.prototype.onIncomingIq = function(iq
) {
599 if (this.plugin_
&& this.plugin_
.postMessage
) {
600 this.plugin_
.postMessage(JSON
.stringify(
601 { method
: 'incomingIq', data
: { iq
: iq
} }));
603 // plugin.onIq may not be set after the plugin has been shut
604 // down. Particularly this happens when we receive response to
605 // session-terminate stanza.
606 console
.warn('plugin.onIq is not set so dropping incoming message.');
611 * @param {string} hostJid The jid of the host to connect to.
612 * @param {string} hostPublicKey The base64 encoded version of the host's
614 * @param {string} localJid Local jid.
615 * @param {string} sharedSecret The access code for IT2Me or the PIN
617 * @param {string} authenticationMethods Comma-separated list of
618 * authentication methods the client should attempt to use.
619 * @param {string} authenticationTag A host-specific tag to mix into
620 * authentication hashes.
621 * @param {string} clientPairingId For paired Me2Me connections, the
622 * pairing id for this client, as issued by the host.
623 * @param {string} clientPairedSecret For paired Me2Me connections, the
624 * paired secret for this client, as issued by the host.
626 remoting
.ClientPluginImpl
.prototype.connect = function(
627 hostJid
, hostPublicKey
, localJid
, sharedSecret
,
628 authenticationMethods
, authenticationTag
,
629 clientPairingId
, clientPairedSecret
) {
631 if (remoting
.platformIsMac()) {
633 } else if (remoting
.platformIsChromeOS()) {
636 this.plugin_
.postMessage(JSON
.stringify(
637 { method
: 'delegateLargeCursors', data
: {} }));
638 this.plugin_
.postMessage(JSON
.stringify(
639 { method
: 'connect', data
: {
641 hostPublicKey
: hostPublicKey
,
643 sharedSecret
: sharedSecret
,
644 authenticationMethods
: authenticationMethods
,
645 authenticationTag
: authenticationTag
,
646 capabilities
: this.capabilities_
.join(" "),
647 clientPairingId
: clientPairingId
,
648 clientPairedSecret
: clientPairedSecret
,
655 * Release all currently pressed keys.
657 remoting
.ClientPluginImpl
.prototype.releaseAllKeys = function() {
658 this.plugin_
.postMessage(JSON
.stringify(
659 { method
: 'releaseAllKeys', data
: {} }));
663 * Send a key event to the host.
665 * @param {number} usbKeycode The USB-style code of the key to inject.
666 * @param {boolean} pressed True to inject a key press, False for a release.
668 remoting
.ClientPluginImpl
.prototype.injectKeyEvent
=
669 function(usbKeycode
, pressed
) {
670 this.plugin_
.postMessage(JSON
.stringify(
671 { method
: 'injectKeyEvent', data
: {
672 'usbKeycode': usbKeycode
,
678 * Remap one USB keycode to another in all subsequent key events.
680 * @param {number} fromKeycode The USB-style code of the key to remap.
681 * @param {number} toKeycode The USB-style code to remap the key to.
683 remoting
.ClientPluginImpl
.prototype.remapKey
=
684 function(fromKeycode
, toKeycode
) {
685 this.plugin_
.postMessage(JSON
.stringify(
686 { method
: 'remapKey', data
: {
687 'fromKeycode': fromKeycode
,
688 'toKeycode': toKeycode
}
693 * Enable/disable redirection of the specified key to the web-app.
695 * @param {number} keycode The USB-style code of the key.
696 * @param {Boolean} trap True to enable trapping, False to disable.
698 remoting
.ClientPluginImpl
.prototype.trapKey = function(keycode
, trap
) {
699 this.plugin_
.postMessage(JSON
.stringify(
700 { method
: 'trapKey', data
: {
707 * Returns an associative array with a set of stats for this connecton.
709 * @return {remoting.ClientSession.PerfStats} The connection statistics.
711 remoting
.ClientPluginImpl
.prototype.getPerfStats = function() {
712 return this.perfStats_
;
716 * Sends a clipboard item to the host.
718 * @param {string} mimeType The MIME type of the clipboard item.
719 * @param {string} item The clipboard item.
721 remoting
.ClientPluginImpl
.prototype.sendClipboardItem
=
722 function(mimeType
, item
) {
723 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.SEND_CLIPBOARD_ITEM
))
725 this.plugin_
.postMessage(JSON
.stringify(
726 { method
: 'sendClipboardItem',
727 data
: { mimeType
: mimeType
, item
: item
}}));
731 * Notifies the host that the client has the specified size and pixel density.
733 * @param {number} width The available client width in DIPs.
734 * @param {number} height The available client height in DIPs.
735 * @param {number} device_scale The number of device pixels per DIP.
737 remoting
.ClientPluginImpl
.prototype.notifyClientResolution
=
738 function(width
, height
, device_scale
) {
739 if (this.hasFeature(remoting
.ClientPlugin
.Feature
.NOTIFY_CLIENT_RESOLUTION
)) {
740 var dpi
= Math
.floor(device_scale
* 96);
741 this.plugin_
.postMessage(JSON
.stringify(
742 { method
: 'notifyClientResolution',
743 data
: { width
: Math
.floor(width
* device_scale
),
744 height
: Math
.floor(height
* device_scale
),
745 x_dpi
: dpi
, y_dpi
: dpi
}}));
750 * Requests that the host pause or resume sending video updates.
752 * @param {boolean} pause True to suspend video updates, false otherwise.
754 remoting
.ClientPluginImpl
.prototype.pauseVideo
=
756 if (this.hasFeature(remoting
.ClientPlugin
.Feature
.VIDEO_CONTROL
)) {
757 this.plugin_
.postMessage(JSON
.stringify(
758 { method
: 'videoControl', data
: { pause
: pause
}}));
759 } else if (this.hasFeature(remoting
.ClientPlugin
.Feature
.PAUSE_VIDEO
)) {
760 this.plugin_
.postMessage(JSON
.stringify(
761 { method
: 'pauseVideo', data
: { pause
: pause
}}));
766 * Requests that the host pause or resume sending audio updates.
768 * @param {boolean} pause True to suspend audio updates, false otherwise.
770 remoting
.ClientPluginImpl
.prototype.pauseAudio
=
772 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.PAUSE_AUDIO
)) {
775 this.plugin_
.postMessage(JSON
.stringify(
776 { method
: 'pauseAudio', data
: { pause
: pause
}}));
780 * Requests that the host configure the video codec for lossless encode.
782 * @param {boolean} wantLossless True to request lossless encoding.
784 remoting
.ClientPluginImpl
.prototype.setLosslessEncode
=
785 function(wantLossless
) {
786 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.VIDEO_CONTROL
)) {
789 this.plugin_
.postMessage(JSON
.stringify(
790 { method
: 'videoControl', data
: { losslessEncode
: wantLossless
}}));
794 * Requests that the host configure the video codec for lossless color.
796 * @param {boolean} wantLossless True to request lossless color.
798 remoting
.ClientPluginImpl
.prototype.setLosslessColor
=
799 function(wantLossless
) {
800 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.VIDEO_CONTROL
)) {
803 this.plugin_
.postMessage(JSON
.stringify(
804 { method
: 'videoControl', data
: { losslessColor
: wantLossless
}}));
808 * Called when a PIN is obtained from the user.
810 * @param {string} pin The PIN.
812 remoting
.ClientPluginImpl
.prototype.onPinFetched
=
814 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.ASYNC_PIN
)) {
817 this.plugin_
.postMessage(JSON
.stringify(
818 { method
: 'onPinFetched', data
: { pin
: pin
}}));
822 * Tells the plugin to ask for the PIN asynchronously.
824 remoting
.ClientPluginImpl
.prototype.useAsyncPinDialog
=
826 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.ASYNC_PIN
)) {
829 this.plugin_
.postMessage(JSON
.stringify(
830 { method
: 'useAsyncPinDialog', data
: {} }));
834 * Sets the third party authentication token and shared secret.
836 * @param {string} token The token received from the token URL.
837 * @param {string} sharedSecret Shared secret received from the token URL.
839 remoting
.ClientPluginImpl
.prototype.onThirdPartyTokenFetched = function(
840 token
, sharedSecret
) {
841 this.plugin_
.postMessage(JSON
.stringify(
842 { method
: 'onThirdPartyTokenFetched',
843 data
: { token
: token
, sharedSecret
: sharedSecret
}}));
847 * Request pairing with the host for PIN-less authentication.
849 * @param {string} clientName The human-readable name of the client.
850 * @param {function(string, string):void} onDone, Callback to receive the
851 * client id and shared secret when they are available.
853 remoting
.ClientPluginImpl
.prototype.requestPairing
=
854 function(clientName
, onDone
) {
855 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.PINLESS_AUTH
)) {
858 this.onPairingComplete_
= onDone
;
859 this.plugin_
.postMessage(JSON
.stringify(
860 { method
: 'requestPairing', data
: { clientName
: clientName
} }));
864 * Send an extension message to the host.
866 * @param {string} type The message type.
867 * @param {string} message The message payload.
869 remoting
.ClientPluginImpl
.prototype.sendClientMessage
=
870 function(type
, message
) {
871 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.EXTENSION_MESSAGE
)) {
874 this.plugin_
.postMessage(JSON
.stringify(
875 { method
: 'extensionMessage',
876 data
: { type
: type
, data
: message
} }));
881 * Request MediaStream-based rendering.
883 * @param {remoting.MediaSourceRenderer} mediaSourceRenderer
885 remoting
.ClientPluginImpl
.prototype.enableMediaSourceRendering
=
886 function(mediaSourceRenderer
) {
887 if (!this.hasFeature(remoting
.ClientPlugin
.Feature
.MEDIA_SOURCE_RENDERING
)) {
890 this.mediaSourceRenderer_
= mediaSourceRenderer
;
891 this.plugin_
.postMessage(JSON
.stringify(
892 { method
: 'enableMediaSourceRendering', data
: {} }));
895 remoting
.ClientPluginImpl
.prototype.getDesktopWidth = function() {
896 return this.desktopWidth_
;
899 remoting
.ClientPluginImpl
.prototype.getDesktopHeight = function() {
900 return this.desktopHeight_
;
903 remoting
.ClientPluginImpl
.prototype.getDesktopXDpi = function() {
904 return this.desktopXDpi_
;
907 remoting
.ClientPluginImpl
.prototype.getDesktopYDpi = function() {
908 return this.desktopYDpi_
;
912 * If we haven't yet received a "hello" message from the plugin, change its
913 * size so that the user can confirm it if click-to-play is enabled, or can
914 * see the "this plugin is disabled" message if it is actually disabled.
917 remoting
.ClientPluginImpl
.prototype.showPluginForClickToPlay_ = function() {
918 if (!this.helloReceived_
) {
921 this.plugin_
.style
.width
= width
+ 'px';
922 this.plugin_
.style
.height
= height
+ 'px';
923 // Center the plugin just underneath the "Connnecting..." dialog.
924 var dialog
= document
.getElementById('client-dialog');
925 var dialogRect
= dialog
.getBoundingClientRect();
926 this.plugin_
.style
.top
= (dialogRect
.bottom
+ 16) + 'px';
927 this.plugin_
.style
.left
= (window
.innerWidth
- width
) / 2 + 'px';
928 this.plugin_
.style
.position
= 'fixed';
933 * Undo the CSS rules needed to make the plugin clickable for click-to-play.
936 remoting
.ClientPluginImpl
.prototype.hidePluginForClickToPlay_ = function() {
937 this.plugin_
.style
.width
= '';
938 this.plugin_
.style
.height
= '';
939 this.plugin_
.style
.top
= '';
940 this.plugin_
.style
.left
= '';
941 this.plugin_
.style
.position
= '';
947 * @implements {remoting.ClientPluginFactory}
949 remoting
.DefaultClientPluginFactory = function() {};
952 * @param {Element} container
953 * @param {function(string, string):boolean} onExtensionMessage
954 * @return {remoting.ClientPlugin}
956 remoting
.DefaultClientPluginFactory
.prototype.createPlugin
=
957 function(container
, onExtensionMessage
) {
958 return new remoting
.ClientPluginImpl(container
, onExtensionMessage
);
961 remoting
.DefaultClientPluginFactory
.prototype.preloadPlugin = function() {
962 if (remoting
.settings
.CLIENT_PLUGIN_TYPE
!= 'pnacl') {
966 var plugin
= remoting
.ClientPluginImpl
.createPluginElement_();
967 plugin
.addEventListener(
968 'loadend', function() { document
.body
.removeChild(plugin
); }, false);
969 document
.body
.appendChild(plugin
);