Add VR video fixes see  #2622
	
		
	
				
					
				
			
							parent
							
								
									7263b060d2
								
							
						
					
					
						commit
						a65d4121c2
					
				@ -0,0 +1,27 @@ | 
				
			||||
Want to contribute? Great! First, read this page (including the small print at the end). | 
				
			||||
 | 
				
			||||
### Before you contribute | 
				
			||||
Before we can use your code, you must sign the | 
				
			||||
[Google Individual Contributor License Agreement] | 
				
			||||
(https://cla.developers.google.com/about/google-individual) | 
				
			||||
(CLA), which you can do online. The CLA is necessary mainly because you own the | 
				
			||||
copyright to your changes, even after your contribution becomes part of our | 
				
			||||
codebase, so we need your permission to use and distribute your code. We also | 
				
			||||
need to be sure of various other things—for instance that you'll tell us if you | 
				
			||||
know that your code infringes on other people's patents. You don't have to sign | 
				
			||||
the CLA until after you've submitted your code for review and a member has | 
				
			||||
approved it, but you must do it before we can put your code into our codebase. | 
				
			||||
Before you start working on a larger contribution, you should get in touch with | 
				
			||||
us first through the issue tracker with your idea so that we can help out and | 
				
			||||
possibly guide you. Coordinating up front makes it much easier to avoid | 
				
			||||
frustration later on. | 
				
			||||
 | 
				
			||||
### Code reviews | 
				
			||||
All submissions, including submissions by project members, require review. We | 
				
			||||
use GitHub pull requests for this purpose. | 
				
			||||
 | 
				
			||||
### The small print | 
				
			||||
Contributions made by corporations are covered by a different agreement than | 
				
			||||
the one above, the | 
				
			||||
[Software Grant and Corporate Contributor License Agreement] | 
				
			||||
(https://cla.developers.google.com/about/google-corporate). | 
				
			||||
@ -0,0 +1,202 @@ | 
				
			||||
 | 
				
			||||
                                 Apache License | 
				
			||||
                           Version 2.0, January 2004 | 
				
			||||
                        http://www.apache.org/licenses/ | 
				
			||||
 | 
				
			||||
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | 
				
			||||
 | 
				
			||||
   1. Definitions. | 
				
			||||
 | 
				
			||||
      "License" shall mean the terms and conditions for use, reproduction, | 
				
			||||
      and distribution as defined by Sections 1 through 9 of this document. | 
				
			||||
 | 
				
			||||
      "Licensor" shall mean the copyright owner or entity authorized by | 
				
			||||
      the copyright owner that is granting the License. | 
				
			||||
 | 
				
			||||
      "Legal Entity" shall mean the union of the acting entity and all | 
				
			||||
      other entities that control, are controlled by, or are under common | 
				
			||||
      control with that entity. For the purposes of this definition, | 
				
			||||
      "control" means (i) the power, direct or indirect, to cause the | 
				
			||||
      direction or management of such entity, whether by contract or | 
				
			||||
      otherwise, or (ii) ownership of fifty percent (50%) or more of the | 
				
			||||
      outstanding shares, or (iii) beneficial ownership of such entity. | 
				
			||||
 | 
				
			||||
      "You" (or "Your") shall mean an individual or Legal Entity | 
				
			||||
      exercising permissions granted by this License. | 
				
			||||
 | 
				
			||||
      "Source" form shall mean the preferred form for making modifications, | 
				
			||||
      including but not limited to software source code, documentation | 
				
			||||
      source, and configuration files. | 
				
			||||
 | 
				
			||||
      "Object" form shall mean any form resulting from mechanical | 
				
			||||
      transformation or translation of a Source form, including but | 
				
			||||
      not limited to compiled object code, generated documentation, | 
				
			||||
      and conversions to other media types. | 
				
			||||
 | 
				
			||||
      "Work" shall mean the work of authorship, whether in Source or | 
				
			||||
      Object form, made available under the License, as indicated by a | 
				
			||||
      copyright notice that is included in or attached to the work | 
				
			||||
      (an example is provided in the Appendix below). | 
				
			||||
 | 
				
			||||
      "Derivative Works" shall mean any work, whether in Source or Object | 
				
			||||
      form, that is based on (or derived from) the Work and for which the | 
				
			||||
      editorial revisions, annotations, elaborations, or other modifications | 
				
			||||
      represent, as a whole, an original work of authorship. For the purposes | 
				
			||||
      of this License, Derivative Works shall not include works that remain | 
				
			||||
      separable from, or merely link (or bind by name) to the interfaces of, | 
				
			||||
      the Work and Derivative Works thereof. | 
				
			||||
 | 
				
			||||
      "Contribution" shall mean any work of authorship, including | 
				
			||||
      the original version of the Work and any modifications or additions | 
				
			||||
      to that Work or Derivative Works thereof, that is intentionally | 
				
			||||
      submitted to Licensor for inclusion in the Work by the copyright owner | 
				
			||||
      or by an individual or Legal Entity authorized to submit on behalf of | 
				
			||||
      the copyright owner. For the purposes of this definition, "submitted" | 
				
			||||
      means any form of electronic, verbal, or written communication sent | 
				
			||||
      to the Licensor or its representatives, including but not limited to | 
				
			||||
      communication on electronic mailing lists, source code control systems, | 
				
			||||
      and issue tracking systems that are managed by, or on behalf of, the | 
				
			||||
      Licensor for the purpose of discussing and improving the Work, but | 
				
			||||
      excluding communication that is conspicuously marked or otherwise | 
				
			||||
      designated in writing by the copyright owner as "Not a Contribution." | 
				
			||||
 | 
				
			||||
      "Contributor" shall mean Licensor and any individual or Legal Entity | 
				
			||||
      on behalf of whom a Contribution has been received by Licensor and | 
				
			||||
      subsequently incorporated within the Work. | 
				
			||||
 | 
				
			||||
   2. Grant of Copyright License. Subject to the terms and conditions of | 
				
			||||
      this License, each Contributor hereby grants to You a perpetual, | 
				
			||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable | 
				
			||||
      copyright license to reproduce, prepare Derivative Works of, | 
				
			||||
      publicly display, publicly perform, sublicense, and distribute the | 
				
			||||
      Work and such Derivative Works in Source or Object form. | 
				
			||||
 | 
				
			||||
   3. Grant of Patent License. Subject to the terms and conditions of | 
				
			||||
      this License, each Contributor hereby grants to You a perpetual, | 
				
			||||
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable | 
				
			||||
      (except as stated in this section) patent license to make, have made, | 
				
			||||
      use, offer to sell, sell, import, and otherwise transfer the Work, | 
				
			||||
      where such license applies only to those patent claims licensable | 
				
			||||
      by such Contributor that are necessarily infringed by their | 
				
			||||
      Contribution(s) alone or by combination of their Contribution(s) | 
				
			||||
      with the Work to which such Contribution(s) was submitted. If You | 
				
			||||
      institute patent litigation against any entity (including a | 
				
			||||
      cross-claim or counterclaim in a lawsuit) alleging that the Work | 
				
			||||
      or a Contribution incorporated within the Work constitutes direct | 
				
			||||
      or contributory patent infringement, then any patent licenses | 
				
			||||
      granted to You under this License for that Work shall terminate | 
				
			||||
      as of the date such litigation is filed. | 
				
			||||
 | 
				
			||||
   4. Redistribution. You may reproduce and distribute copies of the | 
				
			||||
      Work or Derivative Works thereof in any medium, with or without | 
				
			||||
      modifications, and in Source or Object form, provided that You | 
				
			||||
      meet the following conditions: | 
				
			||||
 | 
				
			||||
      (a) You must give any other recipients of the Work or | 
				
			||||
          Derivative Works a copy of this License; and | 
				
			||||
 | 
				
			||||
      (b) You must cause any modified files to carry prominent notices | 
				
			||||
          stating that You changed the files; and | 
				
			||||
 | 
				
			||||
      (c) You must retain, in the Source form of any Derivative Works | 
				
			||||
          that You distribute, all copyright, patent, trademark, and | 
				
			||||
          attribution notices from the Source form of the Work, | 
				
			||||
          excluding those notices that do not pertain to any part of | 
				
			||||
          the Derivative Works; and | 
				
			||||
 | 
				
			||||
      (d) If the Work includes a "NOTICE" text file as part of its | 
				
			||||
          distribution, then any Derivative Works that You distribute must | 
				
			||||
          include a readable copy of the attribution notices contained | 
				
			||||
          within such NOTICE file, excluding those notices that do not | 
				
			||||
          pertain to any part of the Derivative Works, in at least one | 
				
			||||
          of the following places: within a NOTICE text file distributed | 
				
			||||
          as part of the Derivative Works; within the Source form or | 
				
			||||
          documentation, if provided along with the Derivative Works; or, | 
				
			||||
          within a display generated by the Derivative Works, if and | 
				
			||||
          wherever such third-party notices normally appear. The contents | 
				
			||||
          of the NOTICE file are for informational purposes only and | 
				
			||||
          do not modify the License. You may add Your own attribution | 
				
			||||
          notices within Derivative Works that You distribute, alongside | 
				
			||||
          or as an addendum to the NOTICE text from the Work, provided | 
				
			||||
          that such additional attribution notices cannot be construed | 
				
			||||
          as modifying the License. | 
				
			||||
 | 
				
			||||
      You may add Your own copyright statement to Your modifications and | 
				
			||||
      may provide additional or different license terms and conditions | 
				
			||||
      for use, reproduction, or distribution of Your modifications, or | 
				
			||||
      for any such Derivative Works as a whole, provided Your use, | 
				
			||||
      reproduction, and distribution of the Work otherwise complies with | 
				
			||||
      the conditions stated in this License. | 
				
			||||
 | 
				
			||||
   5. Submission of Contributions. Unless You explicitly state otherwise, | 
				
			||||
      any Contribution intentionally submitted for inclusion in the Work | 
				
			||||
      by You to the Licensor shall be under the terms and conditions of | 
				
			||||
      this License, without any additional terms or conditions. | 
				
			||||
      Notwithstanding the above, nothing herein shall supersede or modify | 
				
			||||
      the terms of any separate license agreement you may have executed | 
				
			||||
      with Licensor regarding such Contributions. | 
				
			||||
 | 
				
			||||
   6. Trademarks. This License does not grant permission to use the trade | 
				
			||||
      names, trademarks, service marks, or product names of the Licensor, | 
				
			||||
      except as required for reasonable and customary use in describing the | 
				
			||||
      origin of the Work and reproducing the content of the NOTICE file. | 
				
			||||
 | 
				
			||||
   7. Disclaimer of Warranty. Unless required by applicable law or | 
				
			||||
      agreed to in writing, Licensor provides the Work (and each | 
				
			||||
      Contributor provides its Contributions) on an "AS IS" BASIS, | 
				
			||||
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | 
				
			||||
      implied, including, without limitation, any warranties or conditions | 
				
			||||
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | 
				
			||||
      PARTICULAR PURPOSE. You are solely responsible for determining the | 
				
			||||
      appropriateness of using or redistributing the Work and assume any | 
				
			||||
      risks associated with Your exercise of permissions under this License. | 
				
			||||
 | 
				
			||||
   8. Limitation of Liability. In no event and under no legal theory, | 
				
			||||
      whether in tort (including negligence), contract, or otherwise, | 
				
			||||
      unless required by applicable law (such as deliberate and grossly | 
				
			||||
      negligent acts) or agreed to in writing, shall any Contributor be | 
				
			||||
      liable to You for damages, including any direct, indirect, special, | 
				
			||||
      incidental, or consequential damages of any character arising as a | 
				
			||||
      result of this License or out of the use or inability to use the | 
				
			||||
      Work (including but not limited to damages for loss of goodwill, | 
				
			||||
      work stoppage, computer failure or malfunction, or any and all | 
				
			||||
      other commercial damages or losses), even if such Contributor | 
				
			||||
      has been advised of the possibility of such damages. | 
				
			||||
 | 
				
			||||
   9. Accepting Warranty or Additional Liability. While redistributing | 
				
			||||
      the Work or Derivative Works thereof, You may choose to offer, | 
				
			||||
      and charge a fee for, acceptance of support, warranty, indemnity, | 
				
			||||
      or other liability obligations and/or rights consistent with this | 
				
			||||
      License. However, in accepting such obligations, You may act only | 
				
			||||
      on Your own behalf and on Your sole responsibility, not on behalf | 
				
			||||
      of any other Contributor, and only if You agree to indemnify, | 
				
			||||
      defend, and hold each Contributor harmless for any liability | 
				
			||||
      incurred by, or claims asserted against, such Contributor by reason | 
				
			||||
      of your accepting any such warranty or additional liability. | 
				
			||||
 | 
				
			||||
   END OF TERMS AND CONDITIONS | 
				
			||||
 | 
				
			||||
   APPENDIX: How to apply the Apache License to your work. | 
				
			||||
 | 
				
			||||
      To apply the Apache License to your work, attach the following | 
				
			||||
      boilerplate notice, with the fields enclosed by brackets "[]" | 
				
			||||
      replaced with your own identifying information. (Don't include | 
				
			||||
      the brackets!)  The text should be enclosed in the appropriate | 
				
			||||
      comment syntax for the file format. We also recommend that a | 
				
			||||
      file or class name and description of purpose be included on the | 
				
			||||
      same "printed page" as the copyright notice for easier | 
				
			||||
      identification within third-party archives. | 
				
			||||
 | 
				
			||||
   Copyright 2015 Google Inc | 
				
			||||
 | 
				
			||||
   Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
   you may not use this file except in compliance with the License. | 
				
			||||
   You may obtain a copy of the License at | 
				
			||||
 | 
				
			||||
       http://www.apache.org/licenses/LICENSE-2.0 | 
				
			||||
 | 
				
			||||
   Unless required by applicable law or agreed to in writing, software | 
				
			||||
   distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
   See the License for the specific language governing permissions and | 
				
			||||
   limitations under the License. | 
				
			||||
@ -0,0 +1,73 @@ | 
				
			||||
# VR View | 
				
			||||
 | 
				
			||||
[](https://travis-ci.org/googlevr/vrview) | 
				
			||||
[](https://david-dm.org/googlevr/vrview) | 
				
			||||
[](https://david-dm.org/googlevr/vrview?type=dev) | 
				
			||||
 | 
				
			||||
VR View allows you to embed 360 degree VR media into websites on desktop and | 
				
			||||
mobile. For more information, please read the documentation available at | 
				
			||||
<http://developers.google.com/cardboard/vrview>. | 
				
			||||
 | 
				
			||||
# Configuration | 
				
			||||
 | 
				
			||||
A complete list of VR View parameters can be found in the table below. | 
				
			||||
 | 
				
			||||
Name | Type | Parameter description | 
				
			||||
---- | ---- | --------------------- | 
				
			||||
`video` | String | URL to a 360° video file or an adaptive streaming manifest file (.mpd or .m3u8). Exactly one of video or image is required. | 
				
			||||
`image` | String | URL to a 360° image file. Exactly one of video or image is required. | 
				
			||||
`width` | String | String value for the iframe's width attribute. | 
				
			||||
`height` | String | String value for the iframe's height attribute. | 
				
			||||
`preview` | String | (Optional) URL to a preview image for a 360° image file. | 
				
			||||
`is_stereo` | Boolean | (Optional) Indicates whether the content at the image or video URL is stereo or not. | 
				
			||||
`is_debug` | Boolean | (Optional) When true, turns on debug features like rendering hotspots ad showing the FPS meter. | 
				
			||||
`is_vr_off` | Boolean | (Optional) When true, disables the VR mode button. | 
				
			||||
`is_autopan_off` | Boolean | (Optional) When true, disables the autopan introduction on desktop. | 
				
			||||
`default_yaw` | Number | (Optional) Numeric angle in degrees of the initial heading for the 360° content. By default, the camera points at the center of the underlying image. | 
				
			||||
`is_yaw_only` | Boolean | (Optional) When true, prevents roll and pitch. This is intended for stereo panoramas. | 
				
			||||
`loop` | Boolean | (Optional) When false, stops the loop in the video. | 
				
			||||
`hide_fullscreen_button` | Boolean | (Optional) When true, the fullscreen button contained inside the VR View iframe will be hidden. This parameter is useful if the user wants to use VR View's fullscreen workflow (via `vrView.setFullscreen()` callback) with an element outside the iframe.  | 
				
			||||
`volume` | Number | (Optional) The initial volume of the media; it ranges between 0 and 1; zero equals muted. | 
				
			||||
`muted` | Boolean | (Optional) When true, mutes the sound of the video. | 
				
			||||
 | 
				
			||||
# Downloading files | 
				
			||||
 | 
				
			||||
The `gh-pages` branch hosts the built files. Download these instead of linking to these | 
				
			||||
locations, since the directory structure of the repo may change in the future. | 
				
			||||
 | 
				
			||||
* [https://googlevr.github.io/vrview/build/vrview.js](https://googlevr.github.io/vrview/build/vrview.js) | 
				
			||||
* [https://googlevr.github.io/vrview/build/vrview.min.js](https://googlevr.github.io/vrview/build/vrview.min.js) | 
				
			||||
* [https://googlevr.github.io/vrview/build/embed.js](https://googlevr.github.io/vrview/build/embed.js) | 
				
			||||
* [https://googlevr.github.io/vrview/build/embed.min.js](https://googlevr.github.io/vrview/build/embed.min.js) | 
				
			||||
* [https://googlevr.github.io/vrview/build/three.js](https://googlevr.github.io/vrview/build/three.js) | 
				
			||||
* [https://googlevr.github.io/vrview/build/three.min.js](https://googlevr.github.io/vrview/build/three.min.js) | 
				
			||||
* [https://googlevr.github.io/vrview/build/device-motion-sender.min.js](https://googlevr.github.io/vrview/build/device-motion-sender.min.js) | 
				
			||||
 | 
				
			||||
# Building | 
				
			||||
 | 
				
			||||
This project uses `browserify` to manage dependencies and build. `watchify` is | 
				
			||||
especially convenient to preserve the write-and-reload model of development. | 
				
			||||
This package lives in the npm index. | 
				
			||||
 | 
				
			||||
**Current builds are not working on Windows ([#261](https://github.com/googlevr/vrview/issues/261))** | 
				
			||||
 | 
				
			||||
Relevant commands: | 
				
			||||
```shell | 
				
			||||
$ npm run build # builds the iframe embed and JS API (full and minified versions). | 
				
			||||
$ npm run build-api # builds the JS API (full and minified versions). | 
				
			||||
 | 
				
			||||
$ npm run build-min # builds the minified iframe embed. | 
				
			||||
$ npm run build-dev # builds the full iframe embed. | 
				
			||||
 | 
				
			||||
$ npm run build-api-min # builds the minified JS API. | 
				
			||||
$ npm run build-api-dev # builds the full JS API. | 
				
			||||
 | 
				
			||||
$ npm run watch # auto-builds the iframe embed whenever any source changes. | 
				
			||||
$ npm run watch-api # auto-builds the JS API code whenever any source changes. | 
				
			||||
``` | 
				
			||||
As of 2017/06/13, the pre-built js artifacts have been removed from source | 
				
			||||
control. You must run `npm run build` prior to trying any of the examples. | 
				
			||||
 | 
				
			||||
# Release Notes | 
				
			||||
## 2.0.2 | 
				
			||||
Close vulnerability with unsanitized user strings being injected into DOM | 
				
			||||
@ -0,0 +1 @@ | 
				
			||||
function DeviceMotionSender(){if(!this.isIOS_()){return}window.addEventListener("devicemotion",this.onDeviceMotion_.bind(this),false);this.iframes=document.querySelectorAll("iframe.vrview")}DeviceMotionSender.prototype.onDeviceMotion_=function(e){var message={type:"DeviceMotion",deviceMotionEvent:this.cloneDeviceMotionEvent_(e)};for(var i=0;i<this.iframes.length;i++){var iframe=this.iframes[i];var iframeWindow=iframe.contentWindow;if(this.isCrossDomainIframe_(iframe)){iframeWindow.postMessage(message,"*")}}};DeviceMotionSender.prototype.cloneDeviceMotionEvent_=function(e){return{acceleration:{x:e.acceleration.x,y:e.acceleration.y,z:e.acceleration.z},accelerationIncludingGravity:{x:e.accelerationIncludingGravity.x,y:e.accelerationIncludingGravity.y,z:e.accelerationIncludingGravity.z},rotationRate:{alpha:e.rotationRate.alpha,beta:e.rotationRate.beta,gamma:e.rotationRate.gamma},interval:e.interval}};DeviceMotionSender.prototype.isIOS_=function(){return/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream};DeviceMotionSender.prototype.isCrossDomainIframe_=function(iframe){var html=null;try{var doc=iframe.contentDocument||iframe.contentWindow.document;html=doc.body.innerHTML}catch(err){}return html===null};var dms=new DeviceMotionSender; | 
				
			||||
									
										
											File diff suppressed because one or more lines are too long
										
									
								
							
						
									
										
											File diff suppressed because one or more lines are too long
										
									
								
							
						
									
										
											File diff suppressed because one or more lines are too long
										
									
								
							
						
									
										
											File diff suppressed because one or more lines are too long
										
									
								
							
						@ -0,0 +1,995 @@ | 
				
			||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.VRView = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ | 
				
			||||
'use strict'; | 
				
			||||
 | 
				
			||||
var has = Object.prototype.hasOwnProperty; | 
				
			||||
 | 
				
			||||
//
 | 
				
			||||
// We store our EE objects in a plain object whose properties are event names.
 | 
				
			||||
// If `Object.create(null)` is not supported we prefix the event names with a
 | 
				
			||||
// `~` to make sure that the built-in object properties are not overridden or
 | 
				
			||||
// used as an attack vector.
 | 
				
			||||
// We also assume that `Object.create(null)` is available when the event name
 | 
				
			||||
// is an ES6 Symbol.
 | 
				
			||||
//
 | 
				
			||||
var prefix = typeof Object.create !== 'function' ? '~' : false; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Representation of a single EventEmitter function. | 
				
			||||
 * | 
				
			||||
 * @param {Function} fn Event handler to be called. | 
				
			||||
 * @param {Mixed} context Context for function execution. | 
				
			||||
 * @param {Boolean} [once=false] Only emit once | 
				
			||||
 * @api private | 
				
			||||
 */ | 
				
			||||
function EE(fn, context, once) { | 
				
			||||
  this.fn = fn; | 
				
			||||
  this.context = context; | 
				
			||||
  this.once = once || false; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Minimal EventEmitter interface that is molded against the Node.js | 
				
			||||
 * EventEmitter interface. | 
				
			||||
 * | 
				
			||||
 * @constructor | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
function EventEmitter() { /* Nothing to set */ } | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Hold the assigned EventEmitters by name. | 
				
			||||
 * | 
				
			||||
 * @type {Object} | 
				
			||||
 * @private | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype._events = undefined; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Return an array listing the events for which the emitter has registered | 
				
			||||
 * listeners. | 
				
			||||
 * | 
				
			||||
 * @returns {Array} | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype.eventNames = function eventNames() { | 
				
			||||
  var events = this._events | 
				
			||||
    , names = [] | 
				
			||||
    , name; | 
				
			||||
 | 
				
			||||
  if (!events) return names; | 
				
			||||
 | 
				
			||||
  for (name in events) { | 
				
			||||
    if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (Object.getOwnPropertySymbols) { | 
				
			||||
    return names.concat(Object.getOwnPropertySymbols(events)); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return names; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Return a list of assigned event listeners. | 
				
			||||
 * | 
				
			||||
 * @param {String} event The events that should be listed. | 
				
			||||
 * @param {Boolean} exists We only need to know if there are listeners. | 
				
			||||
 * @returns {Array|Boolean} | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype.listeners = function listeners(event, exists) { | 
				
			||||
  var evt = prefix ? prefix + event : event | 
				
			||||
    , available = this._events && this._events[evt]; | 
				
			||||
 | 
				
			||||
  if (exists) return !!available; | 
				
			||||
  if (!available) return []; | 
				
			||||
  if (available.fn) return [available.fn]; | 
				
			||||
 | 
				
			||||
  for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { | 
				
			||||
    ee[i] = available[i].fn; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return ee; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Emit an event to all registered event listeners. | 
				
			||||
 * | 
				
			||||
 * @param {String} event The name of the event. | 
				
			||||
 * @returns {Boolean} Indication if we've emitted an event. | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { | 
				
			||||
  var evt = prefix ? prefix + event : event; | 
				
			||||
 | 
				
			||||
  if (!this._events || !this._events[evt]) return false; | 
				
			||||
 | 
				
			||||
  var listeners = this._events[evt] | 
				
			||||
    , len = arguments.length | 
				
			||||
    , args | 
				
			||||
    , i; | 
				
			||||
 | 
				
			||||
  if ('function' === typeof listeners.fn) { | 
				
			||||
    if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); | 
				
			||||
 | 
				
			||||
    switch (len) { | 
				
			||||
      case 1: return listeners.fn.call(listeners.context), true; | 
				
			||||
      case 2: return listeners.fn.call(listeners.context, a1), true; | 
				
			||||
      case 3: return listeners.fn.call(listeners.context, a1, a2), true; | 
				
			||||
      case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; | 
				
			||||
      case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; | 
				
			||||
      case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    for (i = 1, args = new Array(len -1); i < len; i++) { | 
				
			||||
      args[i - 1] = arguments[i]; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    listeners.fn.apply(listeners.context, args); | 
				
			||||
  } else { | 
				
			||||
    var length = listeners.length | 
				
			||||
      , j; | 
				
			||||
 | 
				
			||||
    for (i = 0; i < length; i++) { | 
				
			||||
      if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); | 
				
			||||
 | 
				
			||||
      switch (len) { | 
				
			||||
        case 1: listeners[i].fn.call(listeners[i].context); break; | 
				
			||||
        case 2: listeners[i].fn.call(listeners[i].context, a1); break; | 
				
			||||
        case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; | 
				
			||||
        default: | 
				
			||||
          if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { | 
				
			||||
            args[j - 1] = arguments[j]; | 
				
			||||
          } | 
				
			||||
 | 
				
			||||
          listeners[i].fn.apply(listeners[i].context, args); | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return true; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Register a new EventListener for the given event. | 
				
			||||
 * | 
				
			||||
 * @param {String} event Name of the event. | 
				
			||||
 * @param {Function} fn Callback function. | 
				
			||||
 * @param {Mixed} [context=this] The context of the function. | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype.on = function on(event, fn, context) { | 
				
			||||
  var listener = new EE(fn, context || this) | 
				
			||||
    , evt = prefix ? prefix + event : event; | 
				
			||||
 | 
				
			||||
  if (!this._events) this._events = prefix ? {} : Object.create(null); | 
				
			||||
  if (!this._events[evt]) this._events[evt] = listener; | 
				
			||||
  else { | 
				
			||||
    if (!this._events[evt].fn) this._events[evt].push(listener); | 
				
			||||
    else this._events[evt] = [ | 
				
			||||
      this._events[evt], listener | 
				
			||||
    ]; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return this; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Add an EventListener that's only called once. | 
				
			||||
 * | 
				
			||||
 * @param {String} event Name of the event. | 
				
			||||
 * @param {Function} fn Callback function. | 
				
			||||
 * @param {Mixed} [context=this] The context of the function. | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype.once = function once(event, fn, context) { | 
				
			||||
  var listener = new EE(fn, context || this, true) | 
				
			||||
    , evt = prefix ? prefix + event : event; | 
				
			||||
 | 
				
			||||
  if (!this._events) this._events = prefix ? {} : Object.create(null); | 
				
			||||
  if (!this._events[evt]) this._events[evt] = listener; | 
				
			||||
  else { | 
				
			||||
    if (!this._events[evt].fn) this._events[evt].push(listener); | 
				
			||||
    else this._events[evt] = [ | 
				
			||||
      this._events[evt], listener | 
				
			||||
    ]; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return this; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Remove event listeners. | 
				
			||||
 * | 
				
			||||
 * @param {String} event The event we want to remove. | 
				
			||||
 * @param {Function} fn The listener that we need to find. | 
				
			||||
 * @param {Mixed} context Only remove listeners matching this context. | 
				
			||||
 * @param {Boolean} once Only remove once listeners. | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { | 
				
			||||
  var evt = prefix ? prefix + event : event; | 
				
			||||
 | 
				
			||||
  if (!this._events || !this._events[evt]) return this; | 
				
			||||
 | 
				
			||||
  var listeners = this._events[evt] | 
				
			||||
    , events = []; | 
				
			||||
 | 
				
			||||
  if (fn) { | 
				
			||||
    if (listeners.fn) { | 
				
			||||
      if ( | 
				
			||||
           listeners.fn !== fn | 
				
			||||
        || (once && !listeners.once) | 
				
			||||
        || (context && listeners.context !== context) | 
				
			||||
      ) { | 
				
			||||
        events.push(listeners); | 
				
			||||
      } | 
				
			||||
    } else { | 
				
			||||
      for (var i = 0, length = listeners.length; i < length; i++) { | 
				
			||||
        if ( | 
				
			||||
             listeners[i].fn !== fn | 
				
			||||
          || (once && !listeners[i].once) | 
				
			||||
          || (context && listeners[i].context !== context) | 
				
			||||
        ) { | 
				
			||||
          events.push(listeners[i]); | 
				
			||||
        } | 
				
			||||
      } | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  //
 | 
				
			||||
  // Reset the array, or remove it completely if we have no more listeners.
 | 
				
			||||
  //
 | 
				
			||||
  if (events.length) { | 
				
			||||
    this._events[evt] = events.length === 1 ? events[0] : events; | 
				
			||||
  } else { | 
				
			||||
    delete this._events[evt]; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return this; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Remove all listeners or only the listeners for the specified event. | 
				
			||||
 * | 
				
			||||
 * @param {String} event The event want to remove all listeners for. | 
				
			||||
 * @api public | 
				
			||||
 */ | 
				
			||||
EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { | 
				
			||||
  if (!this._events) return this; | 
				
			||||
 | 
				
			||||
  if (event) delete this._events[prefix ? prefix + event : event]; | 
				
			||||
  else this._events = prefix ? {} : Object.create(null); | 
				
			||||
 | 
				
			||||
  return this; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
//
 | 
				
			||||
// Alias methods names because people roll like that.
 | 
				
			||||
//
 | 
				
			||||
EventEmitter.prototype.off = EventEmitter.prototype.removeListener; | 
				
			||||
EventEmitter.prototype.addListener = EventEmitter.prototype.on; | 
				
			||||
 | 
				
			||||
//
 | 
				
			||||
// This function doesn't apply anymore.
 | 
				
			||||
//
 | 
				
			||||
EventEmitter.prototype.setMaxListeners = function setMaxListeners() { | 
				
			||||
  return this; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
//
 | 
				
			||||
// Expose the prefix.
 | 
				
			||||
//
 | 
				
			||||
EventEmitter.prefixed = prefix; | 
				
			||||
 | 
				
			||||
//
 | 
				
			||||
// Expose the module.
 | 
				
			||||
//
 | 
				
			||||
if ('undefined' !== typeof module) { | 
				
			||||
  module.exports = EventEmitter; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
},{}],2:[function(_dereq_,module,exports){ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
var Message = _dereq_('../message'); | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sends events to the embedded VR view IFrame via postMessage. Also handles | 
				
			||||
 * messages sent back from the IFrame: | 
				
			||||
 * | 
				
			||||
 *    click: When a hotspot was clicked. | 
				
			||||
 *    modechange: When the user changes viewing mode (VR|Fullscreen|etc). | 
				
			||||
 */ | 
				
			||||
function IFrameMessageSender(iframe) { | 
				
			||||
  if (!iframe) { | 
				
			||||
    console.error('No iframe specified'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  this.iframe = iframe; | 
				
			||||
 | 
				
			||||
  // On iOS, if the iframe is across domains, also send DeviceMotion data.
 | 
				
			||||
  if (this.isIOS_()) { | 
				
			||||
    window.addEventListener('devicemotion', this.onDeviceMotion_.bind(this), false); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sends a message to the associated VR View IFrame. | 
				
			||||
 */ | 
				
			||||
IFrameMessageSender.prototype.send = function(message) { | 
				
			||||
  var iframeWindow = this.iframe.contentWindow; | 
				
			||||
  iframeWindow.postMessage(message, '*'); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
IFrameMessageSender.prototype.onDeviceMotion_ = function(e) { | 
				
			||||
  var message = { | 
				
			||||
    type: Message.DEVICE_MOTION, | 
				
			||||
    deviceMotionEvent: this.cloneDeviceMotionEvent_(e) | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
  this.send(message); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
IFrameMessageSender.prototype.cloneDeviceMotionEvent_ = function(e) { | 
				
			||||
  return { | 
				
			||||
    acceleration: { | 
				
			||||
      x: e.acceleration.x, | 
				
			||||
      y: e.acceleration.y, | 
				
			||||
      z: e.acceleration.z, | 
				
			||||
    }, | 
				
			||||
    accelerationIncludingGravity: { | 
				
			||||
      x: e.accelerationIncludingGravity.x, | 
				
			||||
      y: e.accelerationIncludingGravity.y, | 
				
			||||
      z: e.accelerationIncludingGravity.z, | 
				
			||||
    }, | 
				
			||||
    rotationRate: { | 
				
			||||
      alpha: e.rotationRate.alpha, | 
				
			||||
      beta: e.rotationRate.beta, | 
				
			||||
      gamma: e.rotationRate.gamma, | 
				
			||||
    }, | 
				
			||||
    interval: e.interval, | 
				
			||||
    timeStamp: e.timeStamp | 
				
			||||
  }; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
IFrameMessageSender.prototype.isIOS_ = function() { | 
				
			||||
  return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = IFrameMessageSender; | 
				
			||||
 | 
				
			||||
},{"../message":5}],3:[function(_dereq_,module,exports){ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var Player = _dereq_('./player'); | 
				
			||||
 | 
				
			||||
var VRView = { | 
				
			||||
  Player: Player | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = VRView; | 
				
			||||
 | 
				
			||||
},{"./player":4}],4:[function(_dereq_,module,exports){ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var EventEmitter = _dereq_('eventemitter3'); | 
				
			||||
var IFrameMessageSender = _dereq_('./iframe-message-sender'); | 
				
			||||
var Message = _dereq_('../message'); | 
				
			||||
var Util = _dereq_('../util'); | 
				
			||||
 | 
				
			||||
// Save the executing script. This will be used to calculate the embed URL.
 | 
				
			||||
var CURRENT_SCRIPT_SRC = Util.getCurrentScript().src; | 
				
			||||
var FAKE_FULLSCREEN_CLASS = 'vrview-fake-fullscreen'; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Entry point for the VR View JS API. | 
				
			||||
 * | 
				
			||||
 * Emits the following events: | 
				
			||||
 *    ready: When the player is loaded. | 
				
			||||
 *    modechange: When the viewing mode changes (normal, fullscreen, VR). | 
				
			||||
 *    click (id): When a hotspot is clicked. | 
				
			||||
 */ | 
				
			||||
function Player(selector, contentInfo) { | 
				
			||||
  // Create a VR View iframe depending on the parameters.
 | 
				
			||||
  var iframe = this.createIframe_(contentInfo); | 
				
			||||
  this.iframe = iframe; | 
				
			||||
 | 
				
			||||
  var parentEl = document.querySelector(selector); | 
				
			||||
  parentEl.appendChild(iframe); | 
				
			||||
 | 
				
			||||
  // Make a sender as well, for relying commands to the child IFrame.
 | 
				
			||||
  this.sender = new IFrameMessageSender(iframe); | 
				
			||||
 | 
				
			||||
  // Listen to messages from the IFrame.
 | 
				
			||||
  window.addEventListener('message', this.onMessage_.bind(this), false); | 
				
			||||
 | 
				
			||||
  // Expose a public .isPaused attribute.
 | 
				
			||||
  this.isPaused = false; | 
				
			||||
 | 
				
			||||
  // Expose a public .isMuted attribute.
 | 
				
			||||
  this.isMuted = false; | 
				
			||||
  if (typeof contentInfo.muted !== 'undefined') { | 
				
			||||
    this.isMuted = contentInfo.muted; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Other public attributes
 | 
				
			||||
  this.currentTime = 0; | 
				
			||||
  this.duration = 0; | 
				
			||||
  this.volume = contentInfo.volume != undefined ? contentInfo.volume : 1; | 
				
			||||
 | 
				
			||||
  if (Util.isIOS()) { | 
				
			||||
    this.injectFullscreenStylesheet_(); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
Player.prototype = new EventEmitter(); | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @param pitch {Number} The latitude of center, specified in degrees, between | 
				
			||||
 * -90 and 90, with 0 at the horizon. | 
				
			||||
 * @param yaw {Number} The longitude of center, specified in degrees, between | 
				
			||||
 * -180 and 180, with 0 at the image center. | 
				
			||||
 * @param radius {Number} The radius of the hotspot, specified in meters. | 
				
			||||
 * @param distance {Number} The distance of the hotspot from camera, specified | 
				
			||||
 * in meters. | 
				
			||||
 * @param hotspotId {String} The ID of the hotspot. | 
				
			||||
 */ | 
				
			||||
Player.prototype.addHotspot = function(hotspotId, params) { | 
				
			||||
  // TODO: Add validation to params.
 | 
				
			||||
  var data = { | 
				
			||||
    pitch: params.pitch, | 
				
			||||
    yaw: params.yaw, | 
				
			||||
    radius: params.radius, | 
				
			||||
    distance: params.distance, | 
				
			||||
    id: hotspotId | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.ADD_HOTSPOT, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.play = function() { | 
				
			||||
  this.sender.send({type: Message.PLAY}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.pause = function() { | 
				
			||||
  this.sender.send({type: Message.PAUSE}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Equivalent of HTML5 setSrc(). | 
				
			||||
 * @param {String} contentInfo | 
				
			||||
 */ | 
				
			||||
Player.prototype.setContent = function(contentInfo) { | 
				
			||||
  this.absolutifyPaths_(contentInfo); | 
				
			||||
  var data = { | 
				
			||||
    contentInfo: contentInfo | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.SET_CONTENT, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sets the software volume of the video. 0 is mute, 1 is max. | 
				
			||||
 */ | 
				
			||||
Player.prototype.setVolume = function(volumeLevel) { | 
				
			||||
  var data = { | 
				
			||||
    volumeLevel: volumeLevel | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.SET_VOLUME, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getVolume = function() { | 
				
			||||
  return this.volume; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sets the mute state of the video element. true is muted, false is unmuted. | 
				
			||||
 */ | 
				
			||||
Player.prototype.mute = function(muteState) { | 
				
			||||
  var data = { | 
				
			||||
    muteState: muteState | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.MUTED, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Set the current time of the media being played | 
				
			||||
 * @param {Number} time | 
				
			||||
 */ | 
				
			||||
Player.prototype.setCurrentTime = function(time) { | 
				
			||||
  var data = { | 
				
			||||
    currentTime: time | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.SET_CURRENT_TIME, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getCurrentTime = function() { | 
				
			||||
  return this.currentTime; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getDuration = function() { | 
				
			||||
  return this.duration; | 
				
			||||
}; | 
				
			||||
Player.prototype.setFullscreen = function() { | 
				
			||||
  this.sender.send({type: Message.SET_FULLSCREEN}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Helper for creating an iframe. | 
				
			||||
 * | 
				
			||||
 * @return {IFrameElement} The iframe. | 
				
			||||
 */ | 
				
			||||
Player.prototype.createIframe_ = function(contentInfo) { | 
				
			||||
  this.absolutifyPaths_(contentInfo); | 
				
			||||
 | 
				
			||||
  var iframe = document.createElement('iframe'); | 
				
			||||
  iframe.setAttribute('allowfullscreen', true); | 
				
			||||
  iframe.setAttribute('scrolling', 'no'); | 
				
			||||
  iframe.style.border = 0; | 
				
			||||
 | 
				
			||||
  // Handle iframe size if width and height are specified.
 | 
				
			||||
  if (contentInfo.hasOwnProperty('width')) { | 
				
			||||
    iframe.setAttribute('width', contentInfo.width); | 
				
			||||
    delete contentInfo.width; | 
				
			||||
  } | 
				
			||||
  if (contentInfo.hasOwnProperty('height')) { | 
				
			||||
    iframe.setAttribute('height', contentInfo.height); | 
				
			||||
    delete contentInfo.height; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  var url = this.getEmbedUrl_() + Util.createGetParams(contentInfo); | 
				
			||||
  iframe.src = url; | 
				
			||||
 | 
				
			||||
  return iframe; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.onMessage_ = function(event) { | 
				
			||||
  var message = event.data; | 
				
			||||
  if (!message || !message.type) { | 
				
			||||
    console.warn('Received message with no type.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  var type = message.type.toLowerCase(); | 
				
			||||
  var data = message.data; | 
				
			||||
  switch (type) { | 
				
			||||
    case 'ready': | 
				
			||||
      if (data !== undefined && data.duration !== undefined) { | 
				
			||||
        this.duration = data.duration; | 
				
			||||
      } | 
				
			||||
    case 'modechange': | 
				
			||||
    case 'error': | 
				
			||||
    case 'click': | 
				
			||||
    case 'ended': | 
				
			||||
    case 'getposition': | 
				
			||||
      this.emit(type, data); | 
				
			||||
      break; | 
				
			||||
    case 'volumechange': | 
				
			||||
      this.volume = data; | 
				
			||||
      this.emit('volumechange', data); | 
				
			||||
      break; | 
				
			||||
    case 'muted': | 
				
			||||
      this.isMuted = data; | 
				
			||||
      this.emit('mute', data); | 
				
			||||
      break; | 
				
			||||
    case 'timeupdate': | 
				
			||||
      this.currentTime = data; | 
				
			||||
      this.emit('timeupdate', { | 
				
			||||
        currentTime: this.currentTime, | 
				
			||||
        duration: this.duration | 
				
			||||
      }); | 
				
			||||
      break; | 
				
			||||
    case 'play': | 
				
			||||
    case 'paused': | 
				
			||||
      this.isPaused = data; | 
				
			||||
      if (this.isPaused) { | 
				
			||||
        this.emit('pause', data); | 
				
			||||
      } else { | 
				
			||||
        this.emit('play', data); | 
				
			||||
      } | 
				
			||||
      break; | 
				
			||||
    case 'enter-fullscreen': | 
				
			||||
    case 'enter-vr': | 
				
			||||
      this.setFakeFullscreen_(true); | 
				
			||||
      break; | 
				
			||||
    case 'exit-fullscreen': | 
				
			||||
      this.setFakeFullscreen_(false); | 
				
			||||
      break; | 
				
			||||
    default: | 
				
			||||
      console.warn('Got unknown message of type %s from %s', message.type, message.origin); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Note: iOS doesn't support the fullscreen API. | 
				
			||||
 * In standalone <iframe> mode, VR View emulates fullscreen by redirecting to | 
				
			||||
 * another page. | 
				
			||||
 * In JS API mode, we stretch the iframe to cover the extent of the page using | 
				
			||||
 * CSS. To do this cleanly, we also inject a stylesheet. | 
				
			||||
 */ | 
				
			||||
Player.prototype.setFakeFullscreen_ = function(isFullscreen) { | 
				
			||||
  if (isFullscreen) { | 
				
			||||
    this.iframe.classList.add(FAKE_FULLSCREEN_CLASS); | 
				
			||||
  } else { | 
				
			||||
    this.iframe.classList.remove(FAKE_FULLSCREEN_CLASS); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.injectFullscreenStylesheet_ = function() { | 
				
			||||
  var styleString = [ | 
				
			||||
    'iframe.' + FAKE_FULLSCREEN_CLASS, | 
				
			||||
    '{', | 
				
			||||
      'position: fixed !important;', | 
				
			||||
      'display: block !important;', | 
				
			||||
      'z-index: 9999999999 !important;', | 
				
			||||
      'top: 0 !important;', | 
				
			||||
      'left: 0 !important;', | 
				
			||||
      'width: 100% !important;', | 
				
			||||
      'height: 100% !important;', | 
				
			||||
      'margin: 0 !important;', | 
				
			||||
    '}', | 
				
			||||
  ].join('\n'); | 
				
			||||
  var style = document.createElement('style'); | 
				
			||||
  style.innerHTML = styleString; | 
				
			||||
  document.body.appendChild(style); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getEmbedUrl_ = function() { | 
				
			||||
  // Assume that the script is in $ROOT/build/something.js, and that the iframe
 | 
				
			||||
  // HTML is in $ROOT/index.html.
 | 
				
			||||
  //
 | 
				
			||||
  // E.g: /vrview/2.0/build/vrview.min.js => /vrview/2.0/index.html.
 | 
				
			||||
  var path = CURRENT_SCRIPT_SRC; | 
				
			||||
  var split = path.split('/'); | 
				
			||||
  var rootSplit = split.slice(0, split.length - 2); | 
				
			||||
  var rootPath = rootSplit.join('/'); | 
				
			||||
  return rootPath + '/index.html'; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getDirName_ = function() { | 
				
			||||
  var path = window.location.pathname; | 
				
			||||
  path = path.substring(0, path.lastIndexOf('/')); | 
				
			||||
  return location.protocol + '//' + location.host + path; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Make all of the URLs inside contentInfo absolute instead of relative. | 
				
			||||
 */ | 
				
			||||
Player.prototype.absolutifyPaths_ = function(contentInfo) { | 
				
			||||
  var dirName = this.getDirName_(); | 
				
			||||
  var urlParams = ['image', 'preview', 'video']; | 
				
			||||
 | 
				
			||||
  for (var i = 0; i < urlParams.length; i++) { | 
				
			||||
    var name = urlParams[i]; | 
				
			||||
    var path = contentInfo[name]; | 
				
			||||
    if (path && Util.isPathAbsolute(path)) { | 
				
			||||
      var absolute = Util.relativeToAbsolutePath(dirName, path); | 
				
			||||
      contentInfo[name] = absolute; | 
				
			||||
      //console.log('Converted to absolute: %s', absolute);
 | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
/** | 
				
			||||
 * Get position YAW, PITCH | 
				
			||||
 */ | 
				
			||||
Player.prototype.getPosition = function() { | 
				
			||||
    this.sender.send({type: Message.GET_POSITION, data: {}}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = Player; | 
				
			||||
 | 
				
			||||
},{"../message":5,"../util":6,"./iframe-message-sender":2,"eventemitter3":1}],5:[function(_dereq_,module,exports){ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Messages from the API to the embed. | 
				
			||||
 */ | 
				
			||||
var Message = { | 
				
			||||
  PLAY: 'play', | 
				
			||||
  PAUSE: 'pause', | 
				
			||||
  TIMEUPDATE: 'timeupdate', | 
				
			||||
  ADD_HOTSPOT: 'addhotspot', | 
				
			||||
  SET_CONTENT: 'setimage', | 
				
			||||
  SET_VOLUME: 'setvolume', | 
				
			||||
  MUTED: 'muted', | 
				
			||||
  SET_CURRENT_TIME: 'setcurrenttime', | 
				
			||||
  DEVICE_MOTION: 'devicemotion', | 
				
			||||
  GET_POSITION: 'getposition', | 
				
			||||
  SET_FULLSCREEN: 'setfullscreen', | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = Message; | 
				
			||||
 | 
				
			||||
},{}],6:[function(_dereq_,module,exports){ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var Util = window.Util || {}; | 
				
			||||
 | 
				
			||||
Util.isDataURI = function(src) { | 
				
			||||
  return src && src.indexOf('data:') == 0; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.generateUUID = function() { | 
				
			||||
  function s4() { | 
				
			||||
    return Math.floor((1 + Math.random()) * 0x10000) | 
				
			||||
    .toString(16) | 
				
			||||
    .substring(1); | 
				
			||||
  } | 
				
			||||
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + | 
				
			||||
    s4() + '-' + s4() + s4() + s4(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isMobile = function() { | 
				
			||||
  var check = false; | 
				
			||||
  (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera); | 
				
			||||
  return check; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isIOS = function() { | 
				
			||||
  return /(iPad|iPhone|iPod)/g.test(navigator.userAgent); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isSafari = function() { | 
				
			||||
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.cloneObject = function(obj) { | 
				
			||||
  var out = {}; | 
				
			||||
  for (key in obj) { | 
				
			||||
    out[key] = obj[key]; | 
				
			||||
  } | 
				
			||||
  return out; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.hashCode = function(s) { | 
				
			||||
  return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.loadTrackSrc = function(context, src, callback, opt_progressCallback) { | 
				
			||||
  var request = new XMLHttpRequest(); | 
				
			||||
  request.open('GET', src, true); | 
				
			||||
  request.responseType = 'arraybuffer'; | 
				
			||||
 | 
				
			||||
  // Decode asynchronously.
 | 
				
			||||
  request.onload = function() { | 
				
			||||
    context.decodeAudioData(request.response, function(buffer) { | 
				
			||||
      callback(buffer); | 
				
			||||
    }, function(e) { | 
				
			||||
      console.error(e); | 
				
			||||
    }); | 
				
			||||
  }; | 
				
			||||
  if (opt_progressCallback) { | 
				
			||||
    request.onprogress = function(e) { | 
				
			||||
      var percent = e.loaded / e.total; | 
				
			||||
      opt_progressCallback(percent); | 
				
			||||
    }; | 
				
			||||
  } | 
				
			||||
  request.send(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isPow2 = function(n) { | 
				
			||||
  return (n & (n - 1)) == 0; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.capitalize = function(s) { | 
				
			||||
  return s.charAt(0).toUpperCase() + s.slice(1); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isIFrame = function() { | 
				
			||||
  try { | 
				
			||||
    return window.self !== window.top; | 
				
			||||
  } catch (e) { | 
				
			||||
    return true; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// From http://goo.gl/4WX3tg
 | 
				
			||||
Util.getQueryParameter = function(name) { | 
				
			||||
  name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); | 
				
			||||
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), | 
				
			||||
      results = regex.exec(location.search); | 
				
			||||
  return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// From http://stackoverflow.com/questions/11871077/proper-way-to-detect-webgl-support.
 | 
				
			||||
Util.isWebGLEnabled = function() { | 
				
			||||
  var canvas = document.createElement('canvas'); | 
				
			||||
  try { gl = canvas.getContext("webgl"); } | 
				
			||||
  catch (x) { gl = null; } | 
				
			||||
 | 
				
			||||
  if (gl == null) { | 
				
			||||
    try { gl = canvas.getContext("experimental-webgl"); experimental = true; } | 
				
			||||
    catch (x) { gl = null; } | 
				
			||||
  } | 
				
			||||
  return !!gl; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.clone = function(obj) { | 
				
			||||
  return JSON.parse(JSON.stringify(obj)); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// From http://stackoverflow.com/questions/10140604/fastest-hypotenuse-in-javascript
 | 
				
			||||
Util.hypot = Math.hypot || function(x, y) { | 
				
			||||
  return Math.sqrt(x*x + y*y); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// From http://stackoverflow.com/a/17447718/693934
 | 
				
			||||
Util.isIE11 = function() { | 
				
			||||
  return navigator.userAgent.match(/Trident/); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getRectCenter = function(rect) { | 
				
			||||
  return new THREE.Vector2(rect.x + rect.width/2, rect.y + rect.height/2); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getScreenWidth = function() { | 
				
			||||
  return Math.max(window.screen.width, window.screen.height) * | 
				
			||||
      window.devicePixelRatio; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getScreenHeight = function() { | 
				
			||||
  return Math.min(window.screen.width, window.screen.height) * | 
				
			||||
      window.devicePixelRatio; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isIOS9OrLess = function() { | 
				
			||||
  if (!Util.isIOS()) { | 
				
			||||
    return false; | 
				
			||||
  } | 
				
			||||
  var re = /(iPhone|iPad|iPod) OS ([\d_]+)/; | 
				
			||||
  var iOSVersion = navigator.userAgent.match(re); | 
				
			||||
  if (!iOSVersion) { | 
				
			||||
    return false; | 
				
			||||
  } | 
				
			||||
  // Get the last group.
 | 
				
			||||
  var versionString = iOSVersion[iOSVersion.length - 1]; | 
				
			||||
  var majorVersion = parseFloat(versionString); | 
				
			||||
  return majorVersion <= 9; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getExtension = function(url) { | 
				
			||||
  return url.split('.').pop().split('?')[0]; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.createGetParams = function(params) { | 
				
			||||
  var out = '?'; | 
				
			||||
  for (var k in params) { | 
				
			||||
    var paramString = k + '=' + params[k] + '&'; | 
				
			||||
    out += paramString; | 
				
			||||
  } | 
				
			||||
  // Remove the trailing ampersand.
 | 
				
			||||
  out.substring(0, params.length - 2); | 
				
			||||
  return out; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.sendParentMessage = function(message) { | 
				
			||||
  if (window.parent) { | 
				
			||||
    parent.postMessage(message, '*'); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.parseBoolean = function(value) { | 
				
			||||
  if (value == 'false' || value == 0) { | 
				
			||||
    return false; | 
				
			||||
  } else if (value == 'true' || value == 1) { | 
				
			||||
    return true; | 
				
			||||
  } else { | 
				
			||||
    return !!value; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @param base {String} An absolute directory root. | 
				
			||||
 * @param relative {String} A relative path. | 
				
			||||
 * | 
				
			||||
 * @returns {String} An absolute path corresponding to the rootPath. | 
				
			||||
 * | 
				
			||||
 * From http://stackoverflow.com/a/14780463/693934.
 | 
				
			||||
 */ | 
				
			||||
Util.relativeToAbsolutePath = function(base, relative) { | 
				
			||||
  var stack = base.split('/'); | 
				
			||||
  var parts = relative.split('/'); | 
				
			||||
  for (var i = 0; i < parts.length; i++) { | 
				
			||||
    if (parts[i] == '.') { | 
				
			||||
      continue; | 
				
			||||
    } | 
				
			||||
    if (parts[i] == '..') { | 
				
			||||
      stack.pop(); | 
				
			||||
    } else { | 
				
			||||
      stack.push(parts[i]); | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  return stack.join('/'); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @return {Boolean} True iff the specified path is an absolute path. | 
				
			||||
 */ | 
				
			||||
Util.isPathAbsolute = function(path) { | 
				
			||||
  return ! /^(?:\/|[a-z]+:\/\/)/.test(path); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
Util.isEmptyObject = function(obj) { | 
				
			||||
  return Object.getOwnPropertyNames(obj).length == 0; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isDebug = function() { | 
				
			||||
  return Util.parseBoolean(Util.getQueryParameter('debug')); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getCurrentScript = function() { | 
				
			||||
  // Note: in IE11, document.currentScript doesn't work, so we fall back to this
 | 
				
			||||
  // hack, taken from https://goo.gl/TpExuH.
 | 
				
			||||
  if (!document.currentScript) { | 
				
			||||
    console.warn('This browser does not support document.currentScript. Trying fallback.'); | 
				
			||||
  } | 
				
			||||
  return document.currentScript || document.scripts[document.scripts.length - 1]; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
module.exports = Util; | 
				
			||||
 | 
				
			||||
},{}]},{},[3])(3) | 
				
			||||
}); | 
				
			||||
									
										
											File diff suppressed because one or more lines are too long
										
									
								
							
						@ -0,0 +1,41 @@ | 
				
			||||
<!DOCTYPE html> | 
				
			||||
<html lang="en"> | 
				
			||||
 | 
				
			||||
<head> | 
				
			||||
  <title>VR view</title> | 
				
			||||
  <meta charset="utf-8"> | 
				
			||||
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | 
				
			||||
  <meta name="mobile-web-app-capable" content="yes"> | 
				
			||||
  <meta name="apple-mobile-web-app-capable" content="yes" /> | 
				
			||||
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> | 
				
			||||
  <link rel="stylesheet" href="style.css"> | 
				
			||||
</head> | 
				
			||||
 | 
				
			||||
<body> | 
				
			||||
  <div id="error" class="dialog"> | 
				
			||||
    <div class="wrap"> | 
				
			||||
      <h1 class="title">Error</h1> | 
				
			||||
      <p class="message">An unknown error occurred.</p> | 
				
			||||
    </div> | 
				
			||||
  </div> | 
				
			||||
 | 
				
			||||
  <div id="play-overlay"> | 
				
			||||
    <img src="images/ic_play_arrow_white_24dp.svg" /> | 
				
			||||
  </div> | 
				
			||||
 | 
				
			||||
  <a id="watermark" href="http://g.co/vr/view" target="_blank"> | 
				
			||||
    <img src="images/ic_info_outline_black_24dp.svg" /> | 
				
			||||
  </a> | 
				
			||||
 | 
				
			||||
  <script> | 
				
			||||
    WebVRConfig = { | 
				
			||||
      BUFFER_SCALE: 0.5, | 
				
			||||
      TOUCH_PANNER_DISABLED: false | 
				
			||||
    }; | 
				
			||||
  </script> | 
				
			||||
 | 
				
			||||
  <script src="build/three.min.js"></script> | 
				
			||||
  <script src="build/embed.min.js"></script> | 
				
			||||
</body> | 
				
			||||
 | 
				
			||||
</html> | 
				
			||||
@ -0,0 +1,41 @@ | 
				
			||||
<!DOCTYPE html> | 
				
			||||
<html lang="en"> | 
				
			||||
 | 
				
			||||
<head> | 
				
			||||
  <title>VR view</title> | 
				
			||||
  <meta charset="utf-8"> | 
				
			||||
  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | 
				
			||||
  <meta name="mobile-web-app-capable" content="yes"> | 
				
			||||
  <meta name="apple-mobile-web-app-capable" content="yes" /> | 
				
			||||
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> | 
				
			||||
  <link rel="stylesheet" href="style.css"> | 
				
			||||
</head> | 
				
			||||
 | 
				
			||||
<body> | 
				
			||||
  <div id="error" class="dialog"> | 
				
			||||
    <div class="wrap"> | 
				
			||||
      <h1 class="title">Error</h1> | 
				
			||||
      <p class="message">An unknown error occurred.</p> | 
				
			||||
    </div> | 
				
			||||
  </div> | 
				
			||||
 | 
				
			||||
  <div id="play-overlay"> | 
				
			||||
    <img src="images/ic_play_arrow_white_24dp.svg" /> | 
				
			||||
  </div> | 
				
			||||
 | 
				
			||||
  <a id="watermark" href="http://g.co/vr/view" target="_blank"> | 
				
			||||
    <img src="images/ic_info_outline_black_24dp.svg" /> | 
				
			||||
  </a> | 
				
			||||
 | 
				
			||||
  <script> | 
				
			||||
    WebVRConfig = { | 
				
			||||
      BUFFER_SCALE: 0.5, | 
				
			||||
      TOUCH_PANNER_DISABLED: false | 
				
			||||
    }; | 
				
			||||
  </script> | 
				
			||||
 | 
				
			||||
  <script src="build/three.js"></script> | 
				
			||||
  <script src="build/embed.js"></script> | 
				
			||||
</body> | 
				
			||||
 | 
				
			||||
</html> | 
				
			||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						@ -0,0 +1,56 @@ | 
				
			||||
{ | 
				
			||||
  "name": "vrview", | 
				
			||||
  "version": "2.0.2", | 
				
			||||
  "description": "Embed VR content into your webpage.", | 
				
			||||
  "main": "index.js", | 
				
			||||
  "dependencies": { | 
				
			||||
    "@tweenjs/tween.js": "^16.8.0", | 
				
			||||
    "es6-promise": "^3.0.2", | 
				
			||||
    "eventemitter3": "^1.2.0", | 
				
			||||
    "lodash": "^4.17.5", | 
				
			||||
    "shaka-player": "^2.0.0", | 
				
			||||
    "stats-js": "^1.0.0-alpha1", | 
				
			||||
    "three": "^0.84.0", | 
				
			||||
    "urijs": "^1.18.2", | 
				
			||||
    "webvr-boilerplate": "^0.4.6", | 
				
			||||
    "webvr-polyfill": "^0.9.38" | 
				
			||||
  }, | 
				
			||||
  "devDependencies": { | 
				
			||||
    "browserify": "^13.1.1", | 
				
			||||
    "debug": "^2.6.9", | 
				
			||||
    "derequire": "^2.0.6", | 
				
			||||
    "eslint": "^4.2.0", | 
				
			||||
    "eslint-config-google": "^0.9.1", | 
				
			||||
    "rollup": "^0.37.2", | 
				
			||||
    "uglify-es": "^3.0.24", | 
				
			||||
    "watchify": "^3.8.0" | 
				
			||||
  }, | 
				
			||||
  "scripts": { | 
				
			||||
    "_mkdir": "mkdir -p build", | 
				
			||||
    "build-min": "npm run _mkdir && browserify src/embed/main.js | derequire | uglifyjs -c > build/embed.min.js && npm run build-three-closure", | 
				
			||||
    "build-dev": "npm run _mkdir && browserify src/embed/main.js | derequire > build/embed.js && npm run build-three-closure", | 
				
			||||
    "build-analytics-min": "npm run _mkdir && browserify src/embed/with-analytics.js | derequire | uglifyjs -c > build/embed.min.js && npm run build-three-closure", | 
				
			||||
    "build-analytics-dev": "npm run _mkdir && browserify src/embed/with-analytics.js | derequire > build/embed.js && npm run build-three-closure", | 
				
			||||
    "build-api-min": "npm run _mkdir && browserify --standalone VRView src/api/main.js | derequire | uglifyjs -c > build/vrview.min.js", | 
				
			||||
    "build-api-dev": "npm run _mkdir && browserify --standalone VRView src/api/main.js | derequire > build/vrview.js", | 
				
			||||
    "build": "npm run build-min; npm run build-dev; npm run build-api", | 
				
			||||
    "build-analytics": "npm run build-analytics-min; npm run build-analytics-dev; npm run build-api", | 
				
			||||
    "build-api": "npm run build-api-min; npm run build-api-dev", | 
				
			||||
    "watch": "npm run _mkdir && watchify src/embed/main.js -v -d -o build/embed.js", | 
				
			||||
    "watch-api": "npm run _mkdir && watchify --standalone VRView src/api/main.js -v -d -o build/vrview.js", | 
				
			||||
    "build-three-closure": "npm run _mkdir && rollup -c src/third_party/three/rollup.config.js && java -jar src/third_party/three/closure-compiler-v20160713.jar --warning_level=VERBOSE --jscomp_off=globalThis --jscomp_off=checkTypes --externs src/third_party/three/externs.js --language_in=ECMASCRIPT5_STRICT --js build/three.js --js_output_file build/three.min.js", | 
				
			||||
    "build-dms": "npm run _mkdir && uglifyjs scripts/js/device-motion-sender.js > build/device-motion-sender.min.js", | 
				
			||||
    "build-all": "npm run build-analytics; npm run build-three-closure; npm run build-dms", | 
				
			||||
    "test": "npm run build" | 
				
			||||
  }, | 
				
			||||
  "repository": { | 
				
			||||
    "type": "git", | 
				
			||||
    "url": "git+https://github.com/google/vrview.git" | 
				
			||||
  }, | 
				
			||||
  "author": "", | 
				
			||||
  "license": "Apache-2.0", | 
				
			||||
  "bugs": { | 
				
			||||
    "url": "https://github.com/google/vrview/issues" | 
				
			||||
  }, | 
				
			||||
  "homepage": "https://github.com/google/vrview#readme" | 
				
			||||
} | 
				
			||||
@ -0,0 +1,21 @@ | 
				
			||||
[ | 
				
			||||
  { | 
				
			||||
    "entity": "allUsers", | 
				
			||||
    "role": "READER" | 
				
			||||
  }, | 
				
			||||
  { | 
				
			||||
    "email": "smus@google.com", | 
				
			||||
    "entity": "user-smus@google.com", | 
				
			||||
    "role": "OWNER" | 
				
			||||
  }, | 
				
			||||
  { | 
				
			||||
    "email": "bwuest@google.com", | 
				
			||||
    "entity": "user-bwuest@google.com", | 
				
			||||
    "role": "OWNER" | 
				
			||||
  }, | 
				
			||||
  { | 
				
			||||
    "email": "nathanmartz@google.com", | 
				
			||||
    "entity": "user-nathanmartz@google.com", | 
				
			||||
    "role": "OWNER" | 
				
			||||
  } | 
				
			||||
] | 
				
			||||
@ -0,0 +1,21 @@ | 
				
			||||
#!/usr/bin/env sh | 
				
			||||
# | 
				
			||||
# Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
# Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
# you may not use this file except in compliance with the License. | 
				
			||||
# You may obtain a copy of the License at | 
				
			||||
# | 
				
			||||
#     http://www.apache.org/licenses/LICENSE-2.0 | 
				
			||||
# | 
				
			||||
# Unless required by applicable law or agreed to in writing, software | 
				
			||||
# distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
# See the License for the specific language governing permissions and | 
				
			||||
# limitations under the License. | 
				
			||||
 | 
				
			||||
 | 
				
			||||
SCRIPT_DIR=`dirname $BASH_SOURCE` | 
				
			||||
 | 
				
			||||
gsutil cp index-minified.html gs://vrview/2.0/index.html | 
				
			||||
gsutil cp -r style.css images/ build/ examples/ gs://vrview/2.0/ | 
				
			||||
gsutil -m acl set -r $SCRIPT_DIR/acl.txt gs://vrview/2.0/ | 
				
			||||
@ -0,0 +1,88 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sends DeviceMotion events to all embedded VR views via postMessage. Note: | 
				
			||||
 * each iframe must have a class 'vrview'. | 
				
			||||
 * | 
				
			||||
 * This is a workaround for https://bugs.webkit.org/show_bug.cgi?id=150072.
 | 
				
			||||
 */ | 
				
			||||
function DeviceMotionSender() { | 
				
			||||
  // This is an iOS-specific workaround.
 | 
				
			||||
  if (!this.isIOS_()) { | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  window.addEventListener('devicemotion', this.onDeviceMotion_.bind(this), false); | 
				
			||||
 | 
				
			||||
  // Find the right iFrame to send data to.
 | 
				
			||||
  this.iframes = document.querySelectorAll('iframe.vrview'); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
DeviceMotionSender.prototype.onDeviceMotion_ = function(e) { | 
				
			||||
  var message = { | 
				
			||||
    type: 'DeviceMotion', | 
				
			||||
    deviceMotionEvent: this.cloneDeviceMotionEvent_(e) | 
				
			||||
  }; | 
				
			||||
  for (var i = 0; i < this.iframes.length; i++) { | 
				
			||||
    // Only send data if we're on iOS and we are dealing with a cross-domain
 | 
				
			||||
    // iframe.
 | 
				
			||||
    var iframe = this.iframes[i]; | 
				
			||||
    var iframeWindow = iframe.contentWindow; | 
				
			||||
    if (this.isCrossDomainIframe_(iframe)) { | 
				
			||||
      iframeWindow.postMessage(message, '*'); | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
DeviceMotionSender.prototype.cloneDeviceMotionEvent_ = function(e) { | 
				
			||||
  return { | 
				
			||||
    acceleration: { | 
				
			||||
      x: e.acceleration.x, | 
				
			||||
      y: e.acceleration.y, | 
				
			||||
      z: e.acceleration.z, | 
				
			||||
    }, | 
				
			||||
    accelerationIncludingGravity: { | 
				
			||||
      x: e.accelerationIncludingGravity.x, | 
				
			||||
      y: e.accelerationIncludingGravity.y, | 
				
			||||
      z: e.accelerationIncludingGravity.z, | 
				
			||||
    }, | 
				
			||||
    rotationRate: { | 
				
			||||
      alpha: e.rotationRate.alpha, | 
				
			||||
      beta: e.rotationRate.beta, | 
				
			||||
      gamma: e.rotationRate.gamma, | 
				
			||||
    }, | 
				
			||||
    interval: e.interval | 
				
			||||
  }; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
DeviceMotionSender.prototype.isIOS_ = function() { | 
				
			||||
  return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// From http://stackoverflow.com/questions/12381334/foolproof-way-to-detect-if-iframe-is-cross-domain.
 | 
				
			||||
DeviceMotionSender.prototype.isCrossDomainIframe_ = function(iframe) { | 
				
			||||
  var html = null; | 
				
			||||
  try { 
 | 
				
			||||
    var doc = iframe.contentDocument || iframe.contentWindow.document; | 
				
			||||
    html = doc.body.innerHTML; | 
				
			||||
  } catch (err) { | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return (html === null); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
var dms = new DeviceMotionSender(); | 
				
			||||
@ -0,0 +1,2 @@ | 
				
			||||
This directory contains all of the JavaScript that is required for the | 
				
			||||
VR View JavaScript API. | 
				
			||||
@ -0,0 +1,80 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
var Message = require('../message'); | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sends events to the embedded VR view IFrame via postMessage. Also handles | 
				
			||||
 * messages sent back from the IFrame: | 
				
			||||
 * | 
				
			||||
 *    click: When a hotspot was clicked. | 
				
			||||
 *    modechange: When the user changes viewing mode (VR|Fullscreen|etc). | 
				
			||||
 */ | 
				
			||||
function IFrameMessageSender(iframe) { | 
				
			||||
  if (!iframe) { | 
				
			||||
    console.error('No iframe specified'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  this.iframe = iframe; | 
				
			||||
 | 
				
			||||
  // On iOS, if the iframe is across domains, also send DeviceMotion data.
 | 
				
			||||
  if (this.isIOS_()) { | 
				
			||||
    window.addEventListener('devicemotion', this.onDeviceMotion_.bind(this), false); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sends a message to the associated VR View IFrame. | 
				
			||||
 */ | 
				
			||||
IFrameMessageSender.prototype.send = function(message) { | 
				
			||||
  var iframeWindow = this.iframe.contentWindow; | 
				
			||||
  iframeWindow.postMessage(message, '*'); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
IFrameMessageSender.prototype.onDeviceMotion_ = function(e) { | 
				
			||||
  var message = { | 
				
			||||
    type: Message.DEVICE_MOTION, | 
				
			||||
    deviceMotionEvent: this.cloneDeviceMotionEvent_(e) | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
  this.send(message); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
IFrameMessageSender.prototype.cloneDeviceMotionEvent_ = function(e) { | 
				
			||||
  return { | 
				
			||||
    acceleration: { | 
				
			||||
      x: e.acceleration.x, | 
				
			||||
      y: e.acceleration.y, | 
				
			||||
      z: e.acceleration.z, | 
				
			||||
    }, | 
				
			||||
    accelerationIncludingGravity: { | 
				
			||||
      x: e.accelerationIncludingGravity.x, | 
				
			||||
      y: e.accelerationIncludingGravity.y, | 
				
			||||
      z: e.accelerationIncludingGravity.z, | 
				
			||||
    }, | 
				
			||||
    rotationRate: { | 
				
			||||
      alpha: e.rotationRate.alpha, | 
				
			||||
      beta: e.rotationRate.beta, | 
				
			||||
      gamma: e.rotationRate.gamma, | 
				
			||||
    }, | 
				
			||||
    interval: e.interval, | 
				
			||||
    timeStamp: e.timeStamp | 
				
			||||
  }; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
IFrameMessageSender.prototype.isIOS_ = function() { | 
				
			||||
  return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = IFrameMessageSender; | 
				
			||||
@ -0,0 +1,22 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var Player = require('./player'); | 
				
			||||
 | 
				
			||||
var VRView = { | 
				
			||||
  Player: Player | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = VRView; | 
				
			||||
@ -0,0 +1,316 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var EventEmitter = require('eventemitter3'); | 
				
			||||
var IFrameMessageSender = require('./iframe-message-sender'); | 
				
			||||
var Message = require('../message'); | 
				
			||||
var Util = require('../util'); | 
				
			||||
 | 
				
			||||
// Save the executing script. This will be used to calculate the embed URL.
 | 
				
			||||
var CURRENT_SCRIPT_SRC = Util.getCurrentScript().src; | 
				
			||||
var FAKE_FULLSCREEN_CLASS = 'vrview-fake-fullscreen'; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Entry point for the VR View JS API. | 
				
			||||
 * | 
				
			||||
 * Emits the following events: | 
				
			||||
 *    ready: When the player is loaded. | 
				
			||||
 *    modechange: When the viewing mode changes (normal, fullscreen, VR). | 
				
			||||
 *    click (id): When a hotspot is clicked. | 
				
			||||
 */ | 
				
			||||
function Player(selector, contentInfo) { | 
				
			||||
  // Create a VR View iframe depending on the parameters.
 | 
				
			||||
  var iframe = this.createIframe_(contentInfo); | 
				
			||||
  this.iframe = iframe; | 
				
			||||
 | 
				
			||||
  var parentEl = document.querySelector(selector); | 
				
			||||
  parentEl.appendChild(iframe); | 
				
			||||
 | 
				
			||||
  // Make a sender as well, for relying commands to the child IFrame.
 | 
				
			||||
  this.sender = new IFrameMessageSender(iframe); | 
				
			||||
 | 
				
			||||
  // Listen to messages from the IFrame.
 | 
				
			||||
  window.addEventListener('message', this.onMessage_.bind(this), false); | 
				
			||||
 | 
				
			||||
  // Expose a public .isPaused attribute.
 | 
				
			||||
  this.isPaused = false; | 
				
			||||
 | 
				
			||||
  // Expose a public .isMuted attribute.
 | 
				
			||||
  this.isMuted = false; | 
				
			||||
  if (typeof contentInfo.muted !== 'undefined') { | 
				
			||||
    this.isMuted = contentInfo.muted; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Other public attributes
 | 
				
			||||
  this.currentTime = 0; | 
				
			||||
  this.duration = 0; | 
				
			||||
  this.volume = contentInfo.volume != undefined ? contentInfo.volume : 1; | 
				
			||||
 | 
				
			||||
  if (Util.isIOS()) { | 
				
			||||
    this.injectFullscreenStylesheet_(); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
Player.prototype = new EventEmitter(); | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @param pitch {Number} The latitude of center, specified in degrees, between | 
				
			||||
 * -90 and 90, with 0 at the horizon. | 
				
			||||
 * @param yaw {Number} The longitude of center, specified in degrees, between | 
				
			||||
 * -180 and 180, with 0 at the image center. | 
				
			||||
 * @param radius {Number} The radius of the hotspot, specified in meters. | 
				
			||||
 * @param distance {Number} The distance of the hotspot from camera, specified | 
				
			||||
 * in meters. | 
				
			||||
 * @param hotspotId {String} The ID of the hotspot. | 
				
			||||
 */ | 
				
			||||
Player.prototype.addHotspot = function(hotspotId, params) { | 
				
			||||
  // TODO: Add validation to params.
 | 
				
			||||
  var data = { | 
				
			||||
    pitch: params.pitch, | 
				
			||||
    yaw: params.yaw, | 
				
			||||
    radius: params.radius, | 
				
			||||
    distance: params.distance, | 
				
			||||
    id: hotspotId | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.ADD_HOTSPOT, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.play = function() { | 
				
			||||
  this.sender.send({type: Message.PLAY}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.pause = function() { | 
				
			||||
  this.sender.send({type: Message.PAUSE}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Equivalent of HTML5 setSrc(). | 
				
			||||
 * @param {String} contentInfo | 
				
			||||
 */ | 
				
			||||
Player.prototype.setContent = function(contentInfo) { | 
				
			||||
  this.absolutifyPaths_(contentInfo); | 
				
			||||
  var data = { | 
				
			||||
    contentInfo: contentInfo | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.SET_CONTENT, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sets the software volume of the video. 0 is mute, 1 is max. | 
				
			||||
 */ | 
				
			||||
Player.prototype.setVolume = function(volumeLevel) { | 
				
			||||
  var data = { | 
				
			||||
    volumeLevel: volumeLevel | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.SET_VOLUME, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getVolume = function() { | 
				
			||||
  return this.volume; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sets the mute state of the video element. true is muted, false is unmuted. | 
				
			||||
 */ | 
				
			||||
Player.prototype.mute = function(muteState) { | 
				
			||||
  var data = { | 
				
			||||
    muteState: muteState | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.MUTED, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Set the current time of the media being played | 
				
			||||
 * @param {Number} time | 
				
			||||
 */ | 
				
			||||
Player.prototype.setCurrentTime = function(time) { | 
				
			||||
  var data = { | 
				
			||||
    currentTime: time | 
				
			||||
  }; | 
				
			||||
  this.sender.send({type: Message.SET_CURRENT_TIME, data: data}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getCurrentTime = function() { | 
				
			||||
  return this.currentTime; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getDuration = function() { | 
				
			||||
  return this.duration; | 
				
			||||
}; | 
				
			||||
Player.prototype.setFullscreen = function() { | 
				
			||||
  this.sender.send({type: Message.SET_FULLSCREEN}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Helper for creating an iframe. | 
				
			||||
 * | 
				
			||||
 * @return {IFrameElement} The iframe. | 
				
			||||
 */ | 
				
			||||
Player.prototype.createIframe_ = function(contentInfo) { | 
				
			||||
  this.absolutifyPaths_(contentInfo); | 
				
			||||
 | 
				
			||||
  var iframe = document.createElement('iframe'); | 
				
			||||
  iframe.setAttribute('allowfullscreen', true); | 
				
			||||
  iframe.setAttribute('scrolling', 'no'); | 
				
			||||
  iframe.style.border = 0; | 
				
			||||
 | 
				
			||||
  // Handle iframe size if width and height are specified.
 | 
				
			||||
  if (contentInfo.hasOwnProperty('width')) { | 
				
			||||
    iframe.setAttribute('width', contentInfo.width); | 
				
			||||
    delete contentInfo.width; | 
				
			||||
  } | 
				
			||||
  if (contentInfo.hasOwnProperty('height')) { | 
				
			||||
    iframe.setAttribute('height', contentInfo.height); | 
				
			||||
    delete contentInfo.height; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  var url = this.getEmbedUrl_() + Util.createGetParams(contentInfo); | 
				
			||||
  iframe.src = url; | 
				
			||||
 | 
				
			||||
  return iframe; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.onMessage_ = function(event) { | 
				
			||||
  var message = event.data; | 
				
			||||
  if (!message || !message.type) { | 
				
			||||
    console.warn('Received message with no type.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  var type = message.type.toLowerCase(); | 
				
			||||
  var data = message.data; | 
				
			||||
  switch (type) { | 
				
			||||
    case 'ready': | 
				
			||||
      if (data !== undefined && data.duration !== undefined) { | 
				
			||||
        this.duration = data.duration; | 
				
			||||
      } | 
				
			||||
    case 'modechange': | 
				
			||||
    case 'error': | 
				
			||||
    case 'click': | 
				
			||||
    case 'ended': | 
				
			||||
    case 'getposition': | 
				
			||||
      this.emit(type, data); | 
				
			||||
      break; | 
				
			||||
    case 'volumechange': | 
				
			||||
      this.volume = data; | 
				
			||||
      this.emit('volumechange', data); | 
				
			||||
      break; | 
				
			||||
    case 'muted': | 
				
			||||
      this.isMuted = data; | 
				
			||||
      this.emit('mute', data); | 
				
			||||
      break; | 
				
			||||
    case 'timeupdate': | 
				
			||||
      this.currentTime = data; | 
				
			||||
      this.emit('timeupdate', { | 
				
			||||
        currentTime: this.currentTime, | 
				
			||||
        duration: this.duration | 
				
			||||
      }); | 
				
			||||
      break; | 
				
			||||
    case 'play': | 
				
			||||
    case 'paused': | 
				
			||||
      this.isPaused = data; | 
				
			||||
      if (this.isPaused) { | 
				
			||||
        this.emit('pause', data); | 
				
			||||
      } else { | 
				
			||||
        this.emit('play', data); | 
				
			||||
      } | 
				
			||||
      break; | 
				
			||||
    case 'enter-fullscreen': | 
				
			||||
    case 'enter-vr': | 
				
			||||
      this.setFakeFullscreen_(true); | 
				
			||||
      break; | 
				
			||||
    case 'exit-fullscreen': | 
				
			||||
      this.setFakeFullscreen_(false); | 
				
			||||
      break; | 
				
			||||
    default: | 
				
			||||
      console.warn('Got unknown message of type %s from %s', message.type, message.origin); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Note: iOS doesn't support the fullscreen API. | 
				
			||||
 * In standalone <iframe> mode, VR View emulates fullscreen by redirecting to | 
				
			||||
 * another page. | 
				
			||||
 * In JS API mode, we stretch the iframe to cover the extent of the page using | 
				
			||||
 * CSS. To do this cleanly, we also inject a stylesheet. | 
				
			||||
 */ | 
				
			||||
Player.prototype.setFakeFullscreen_ = function(isFullscreen) { | 
				
			||||
  if (isFullscreen) { | 
				
			||||
    this.iframe.classList.add(FAKE_FULLSCREEN_CLASS); | 
				
			||||
  } else { | 
				
			||||
    this.iframe.classList.remove(FAKE_FULLSCREEN_CLASS); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.injectFullscreenStylesheet_ = function() { | 
				
			||||
  var styleString = [ | 
				
			||||
    'iframe.' + FAKE_FULLSCREEN_CLASS, | 
				
			||||
    '{', | 
				
			||||
      'position: fixed !important;', | 
				
			||||
      'display: block !important;', | 
				
			||||
      'z-index: 9999999999 !important;', | 
				
			||||
      'top: 0 !important;', | 
				
			||||
      'left: 0 !important;', | 
				
			||||
      'width: 100% !important;', | 
				
			||||
      'height: 100% !important;', | 
				
			||||
      'margin: 0 !important;', | 
				
			||||
    '}', | 
				
			||||
  ].join('\n'); | 
				
			||||
  var style = document.createElement('style'); | 
				
			||||
  style.innerHTML = styleString; | 
				
			||||
  document.body.appendChild(style); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getEmbedUrl_ = function() { | 
				
			||||
  // Assume that the script is in $ROOT/build/something.js, and that the iframe
 | 
				
			||||
  // HTML is in $ROOT/index.html.
 | 
				
			||||
  //
 | 
				
			||||
  // E.g: /vrview/2.0/build/vrview.min.js => /vrview/2.0/index.html.
 | 
				
			||||
  var path = CURRENT_SCRIPT_SRC; | 
				
			||||
  var split = path.split('/'); | 
				
			||||
  var rootSplit = split.slice(0, split.length - 2); | 
				
			||||
  var rootPath = rootSplit.join('/'); | 
				
			||||
  return rootPath + '/index.html'; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Player.prototype.getDirName_ = function() { | 
				
			||||
  var path = window.location.pathname; | 
				
			||||
  path = path.substring(0, path.lastIndexOf('/')); | 
				
			||||
  return location.protocol + '//' + location.host + path; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Make all of the URLs inside contentInfo absolute instead of relative. | 
				
			||||
 */ | 
				
			||||
Player.prototype.absolutifyPaths_ = function(contentInfo) { | 
				
			||||
  var dirName = this.getDirName_(); | 
				
			||||
  var urlParams = ['image', 'preview', 'video']; | 
				
			||||
 | 
				
			||||
  for (var i = 0; i < urlParams.length; i++) { | 
				
			||||
    var name = urlParams[i]; | 
				
			||||
    var path = contentInfo[name]; | 
				
			||||
    if (path && Util.isPathAbsolute(path)) { | 
				
			||||
      var absolute = Util.relativeToAbsolutePath(dirName, path); | 
				
			||||
      contentInfo[name] = absolute; | 
				
			||||
      //console.log('Converted to absolute: %s', absolute);
 | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
/** | 
				
			||||
 * Get position YAW, PITCH | 
				
			||||
 */ | 
				
			||||
Player.prototype.getPosition = function() { | 
				
			||||
    this.sender.send({type: Message.GET_POSITION, data: {}}); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = Player; | 
				
			||||
@ -0,0 +1,147 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var EventEmitter = require('eventemitter3'); | 
				
			||||
var shaka = require('shaka-player'); | 
				
			||||
 | 
				
			||||
var Types = require('../video-type'); | 
				
			||||
var Util = require('../util'); | 
				
			||||
 | 
				
			||||
var DEFAULT_BITS_PER_SECOND = 1000000; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Supports regular video URLs (eg. mp4), as well as adaptive manifests like | 
				
			||||
 * DASH (.mpd) and soon HLS (.m3u8). | 
				
			||||
 * | 
				
			||||
 * Events: | 
				
			||||
 *   load(video): When the video is loaded. | 
				
			||||
 *   error(message): If an error occurs. | 
				
			||||
 * | 
				
			||||
 * To play/pause/seek/etc, please use the underlying video element. | 
				
			||||
 */ | 
				
			||||
function AdaptivePlayer(params) { | 
				
			||||
  this.video = document.createElement('video'); | 
				
			||||
  // Loop by default.
 | 
				
			||||
  if (params.loop === true) { | 
				
			||||
    this.video.setAttribute('loop', true); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (params.volume !== undefined) { | 
				
			||||
    // XXX: .setAttribute('volume', params.volume) doesn't work for some reason.
 | 
				
			||||
    this.video.volume = params.volume; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Not muted by default.
 | 
				
			||||
  if (params.muted === true) { | 
				
			||||
    this.video.muted = params.muted; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // For FF, make sure we enable preload.
 | 
				
			||||
  this.video.setAttribute('preload', 'auto'); | 
				
			||||
  // Enable inline video playback in iOS 10+.
 | 
				
			||||
  this.video.setAttribute('playsinline', true); | 
				
			||||
  this.video.setAttribute('crossorigin', 'anonymous'); | 
				
			||||
} | 
				
			||||
AdaptivePlayer.prototype = new EventEmitter(); | 
				
			||||
 | 
				
			||||
AdaptivePlayer.prototype.load = function(url) { | 
				
			||||
  var self = this; | 
				
			||||
  // TODO(smus): Investigate whether or not differentiation is best done by
 | 
				
			||||
  // mimeType after all. Cursory research suggests that adaptive streaming
 | 
				
			||||
  // manifest mime types aren't properly supported.
 | 
				
			||||
  //
 | 
				
			||||
  // For now, make determination based on extension.
 | 
				
			||||
  var extension = Util.getExtension(url); | 
				
			||||
  switch (extension) { | 
				
			||||
    case 'm3u8': // HLS
 | 
				
			||||
      this.type = Types.HLS; | 
				
			||||
      if (Util.isSafari()) { | 
				
			||||
        this.loadVideo_(url).then(function() { | 
				
			||||
          self.emit('load', self.video, self.type); | 
				
			||||
        }).catch(this.onError_.bind(this)); | 
				
			||||
      } else { | 
				
			||||
        self.onError_('HLS is only supported on Safari.'); | 
				
			||||
      } | 
				
			||||
      break; | 
				
			||||
    case 'mpd': // MPEG-DASH
 | 
				
			||||
      this.type = Types.DASH; | 
				
			||||
      this.loadShakaVideo_(url).then(function() { | 
				
			||||
        console.log('The video has now been loaded!'); | 
				
			||||
        self.emit('load', self.video, self.type); | 
				
			||||
      }).catch(this.onError_.bind(this)); | 
				
			||||
      break; | 
				
			||||
    default: // A regular video, not an adaptive manifest.
 | 
				
			||||
      this.type = Types.VIDEO; | 
				
			||||
      this.loadVideo_(url).then(function() { | 
				
			||||
        self.emit('load', self.video, self.type); | 
				
			||||
      }).catch(this.onError_.bind(this)); | 
				
			||||
      break; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
AdaptivePlayer.prototype.destroy = function() { | 
				
			||||
  this.video.pause(); | 
				
			||||
  this.video.src = ''; | 
				
			||||
  this.video = null; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/*** PRIVATE API ***/ | 
				
			||||
 | 
				
			||||
AdaptivePlayer.prototype.onError_ = function(e) { | 
				
			||||
  console.error(e); | 
				
			||||
  this.emit('error', e); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
AdaptivePlayer.prototype.loadVideo_ = function(url) { | 
				
			||||
  var self = this, video = self.video; | 
				
			||||
  return new Promise(function(resolve, reject) { | 
				
			||||
    video.src = url; | 
				
			||||
    video.addEventListener('canplaythrough', resolve); | 
				
			||||
    video.addEventListener('loadedmetadata', function() { | 
				
			||||
      self.emit('timeupdate', { | 
				
			||||
        currentTime: video.currentTime, | 
				
			||||
        duration: video.duration | 
				
			||||
      }); | 
				
			||||
    }); | 
				
			||||
    video.addEventListener('error', reject); | 
				
			||||
    video.load(); | 
				
			||||
  }); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
AdaptivePlayer.prototype.initShaka_ = function() { | 
				
			||||
  this.player = new shaka.Player(this.video); | 
				
			||||
 | 
				
			||||
  this.player.configure({ | 
				
			||||
    abr: { defaultBandwidthEstimate: DEFAULT_BITS_PER_SECOND } | 
				
			||||
  }); | 
				
			||||
 | 
				
			||||
  // Listen for error events.
 | 
				
			||||
  this.player.addEventListener('error', this.onError_); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
AdaptivePlayer.prototype.loadShakaVideo_ = function(url) { | 
				
			||||
  // Install built-in polyfills to patch browser incompatibilities.
 | 
				
			||||
  shaka.polyfill.installAll(); | 
				
			||||
 | 
				
			||||
  if (!shaka.Player.isBrowserSupported()) { | 
				
			||||
    console.error('Shaka is not supported on this browser.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  this.initShaka_(); | 
				
			||||
  return this.player.load(url); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = AdaptivePlayer; | 
				
			||||
@ -0,0 +1,56 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
function Analytics() { | 
				
			||||
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | 
				
			||||
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | 
				
			||||
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | 
				
			||||
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); | 
				
			||||
 | 
				
			||||
  ga('create', 'UA-35315454-8', 'auto'); | 
				
			||||
  ga('send', 'pageview'); | 
				
			||||
 | 
				
			||||
  this.lastModeChangeTime = window.performance.now(); | 
				
			||||
  this.lastModeLabel = Analytics.MODE_LABELS[0]; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
Analytics.MODE_LABELS = { | 
				
			||||
  0: 'UNKNOWN', | 
				
			||||
  1: 'NORMAL', | 
				
			||||
  2: 'MAGIC_WINDOW', | 
				
			||||
  3: 'VR' | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
Analytics.prototype.logModeChanged = function(mode) { | 
				
			||||
  var modeLabel = Analytics.MODE_LABELS[mode]; | 
				
			||||
  var lastModeLabel = Analytics.MODE_LABELS[this.lastMode]; | 
				
			||||
 | 
				
			||||
  console.log('Analytics: going from mode %s to %s', lastModeLabel, modeLabel); | 
				
			||||
 | 
				
			||||
  ga('send', 'screenview', { | 
				
			||||
    appName: 'EmbedVR', | 
				
			||||
    screenName: modeLabel | 
				
			||||
  }); | 
				
			||||
 | 
				
			||||
  var now = window.performance.now(); | 
				
			||||
  var msSinceLastModeChange = Math.round(now - this.lastModeChangeTime); | 
				
			||||
  ga('send', 'timing', 'Time spent in mode', lastModeLabel, msSinceLastModeChange); | 
				
			||||
 | 
				
			||||
  this.lastModeChangeTime = now; | 
				
			||||
  this.lastMode = mode; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
window.analytics = new Analytics(); | 
				
			||||
@ -0,0 +1,20 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
var Eyes = { | 
				
			||||
  LEFT: 1, | 
				
			||||
  RIGHT: 2 | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = Eyes; | 
				
			||||
@ -0,0 +1,403 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
var EventEmitter = require('eventemitter3'); | 
				
			||||
var TWEEN = require('@tweenjs/tween.js'); | 
				
			||||
 | 
				
			||||
var Util = require('../util'); | 
				
			||||
 | 
				
			||||
// Constants for the focus/blur animation.
 | 
				
			||||
var NORMAL_SCALE = new THREE.Vector3(1, 1, 1); | 
				
			||||
var FOCUS_SCALE = new THREE.Vector3(1.2, 1.2, 1.2); | 
				
			||||
var FOCUS_DURATION = 200; | 
				
			||||
 | 
				
			||||
// Constants for the active/inactive animation.
 | 
				
			||||
var INACTIVE_COLOR = new THREE.Color(1, 1, 1); | 
				
			||||
var ACTIVE_COLOR = new THREE.Color(0.8, 0, 0); | 
				
			||||
var ACTIVE_DURATION = 100; | 
				
			||||
 | 
				
			||||
// Constants for opacity.
 | 
				
			||||
var MAX_INNER_OPACITY = 0.8; | 
				
			||||
var MAX_OUTER_OPACITY = 0.5; | 
				
			||||
var FADE_START_ANGLE_DEG = 35; | 
				
			||||
var FADE_END_ANGLE_DEG = 60; | 
				
			||||
/** | 
				
			||||
 * Responsible for rectangular hot spots that the user can interact with. | 
				
			||||
 * | 
				
			||||
 * Specific duties: | 
				
			||||
 *   Adding and removing hotspots. | 
				
			||||
 *   Rendering the hotspots (debug mode only). | 
				
			||||
 *   Notifying when hotspots are interacted with. | 
				
			||||
 * | 
				
			||||
 * Emits the following events: | 
				
			||||
 *   click (id): a hotspot is clicked. | 
				
			||||
 *   focus (id): a hotspot is focused. | 
				
			||||
 *   blur (id): a hotspot is no longer hovered over. | 
				
			||||
 */ | 
				
			||||
function HotspotRenderer(worldRenderer) { | 
				
			||||
  this.worldRenderer = worldRenderer; | 
				
			||||
  this.scene = worldRenderer.scene; | 
				
			||||
 | 
				
			||||
  // Note: this event must be added to document.body and not to window for it to
 | 
				
			||||
  // work inside iOS iframes.
 | 
				
			||||
  var body = document.body; | 
				
			||||
  // Bind events for hotspot interaction.
 | 
				
			||||
  if (!Util.isMobile()) { | 
				
			||||
    // Only enable mouse events on desktop.
 | 
				
			||||
    body.addEventListener('mousedown', this.onMouseDown_.bind(this), false); | 
				
			||||
    body.addEventListener('mousemove', this.onMouseMove_.bind(this), false); | 
				
			||||
    body.addEventListener('mouseup', this.onMouseUp_.bind(this), false); | 
				
			||||
  } | 
				
			||||
  body.addEventListener('touchstart', this.onTouchStart_.bind(this), false); | 
				
			||||
  body.addEventListener('touchend', this.onTouchEnd_.bind(this), false); | 
				
			||||
 | 
				
			||||
  // Add a placeholder for hotspots.
 | 
				
			||||
  this.hotspotRoot = new THREE.Object3D(); | 
				
			||||
  // Align the center with the center of the camera too.
 | 
				
			||||
  this.hotspotRoot.rotation.y = Math.PI / 2; | 
				
			||||
  this.scene.add(this.hotspotRoot); | 
				
			||||
 | 
				
			||||
  // All hotspot IDs.
 | 
				
			||||
  this.hotspots = {}; | 
				
			||||
 | 
				
			||||
  // Currently selected hotspots.
 | 
				
			||||
  this.selectedHotspots = {}; | 
				
			||||
 | 
				
			||||
  // Hotspots that the last touchstart / mousedown event happened for.
 | 
				
			||||
  this.downHotspots = {}; | 
				
			||||
 | 
				
			||||
  // For raycasting. Initialize mouse to be off screen initially.
 | 
				
			||||
  this.pointer = new THREE.Vector2(1, 1); | 
				
			||||
  this.raycaster = new THREE.Raycaster(); | 
				
			||||
} | 
				
			||||
HotspotRenderer.prototype = new EventEmitter(); | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @param pitch {Number} The latitude of center, specified in degrees, between | 
				
			||||
 * -90 and 90, with 0 at the horizon. | 
				
			||||
 * @param yaw {Number} The longitude of center, specified in degrees, between | 
				
			||||
 * -180 and 180, with 0 at the image center. | 
				
			||||
 * @param radius {Number} The radius of the hotspot, specified in meters. | 
				
			||||
 * @param distance {Number} The distance of the hotspot from camera, specified | 
				
			||||
 * in meters. | 
				
			||||
 * @param hotspotId {String} The ID of the hotspot. | 
				
			||||
 */ | 
				
			||||
HotspotRenderer.prototype.add = function(pitch, yaw, radius, distance, id) { | 
				
			||||
  // If a hotspot already exists with this ID, stop.
 | 
				
			||||
  if (this.hotspots[id]) { | 
				
			||||
    // TODO: Proper error reporting.
 | 
				
			||||
    console.error('Attempt to add hotspot with existing id %s.', id); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  var hotspot = this.createHotspot_(radius, distance); | 
				
			||||
  hotspot.name = id; | 
				
			||||
 | 
				
			||||
  // Position the hotspot based on the pitch and yaw specified.
 | 
				
			||||
  var quat = new THREE.Quaternion(); | 
				
			||||
  quat.setFromEuler(new THREE.Euler(THREE.Math.degToRad(pitch), THREE.Math.degToRad(yaw), 0, 'ZYX')); | 
				
			||||
  hotspot.position.applyQuaternion(quat); | 
				
			||||
  hotspot.lookAt(new THREE.Vector3()); | 
				
			||||
 | 
				
			||||
  this.hotspotRoot.add(hotspot); | 
				
			||||
  this.hotspots[id] = hotspot; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Removes a hotspot based on the ID. | 
				
			||||
 * | 
				
			||||
 * @param ID {String} Identifier of the hotspot to be removed. | 
				
			||||
 */ | 
				
			||||
HotspotRenderer.prototype.remove = function(id) { | 
				
			||||
  // If there's no hotspot with this ID, fail.
 | 
				
			||||
  if (!this.hotspots[id]) { | 
				
			||||
    // TODO: Proper error reporting.
 | 
				
			||||
    console.error('Attempt to remove non-existing hotspot with id %s.', id); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  // Remove the mesh from the scene.
 | 
				
			||||
  this.hotspotRoot.remove(this.hotspots[id]); | 
				
			||||
 | 
				
			||||
  // If this hotspot was selected, make sure it gets unselected.
 | 
				
			||||
  delete this.selectedHotspots[id]; | 
				
			||||
  delete this.downHotspots[id]; | 
				
			||||
  delete this.hotspots[id]; | 
				
			||||
  this.emit('blur', id); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Clears all hotspots from the pano. Often called when changing panos. | 
				
			||||
 */ | 
				
			||||
HotspotRenderer.prototype.clearAll = function() { | 
				
			||||
  for (var id in this.hotspots) { | 
				
			||||
    this.remove(id); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.getCount = function() { | 
				
			||||
  var count = 0; | 
				
			||||
  for (var id in this.hotspots) { | 
				
			||||
    count += 1; | 
				
			||||
  } | 
				
			||||
  return count; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.update = function(camera) { | 
				
			||||
  if (this.worldRenderer.isVRMode()) { | 
				
			||||
    this.pointer.set(0, 0); | 
				
			||||
  } | 
				
			||||
  // Update the picking ray with the camera and mouse position.
 | 
				
			||||
  this.raycaster.setFromCamera(this.pointer, camera); | 
				
			||||
 | 
				
			||||
  // Fade hotspots out if they are really far from center to avoid overly
 | 
				
			||||
  // distorted visuals.
 | 
				
			||||
  this.fadeOffCenterHotspots_(camera); | 
				
			||||
 | 
				
			||||
  var hotspots = this.hotspotRoot.children; | 
				
			||||
 | 
				
			||||
  // Go through all hotspots to see if they are currently selected.
 | 
				
			||||
  for (var i = 0; i < hotspots.length; i++) { | 
				
			||||
    var hotspot = hotspots[i]; | 
				
			||||
    //hotspot.lookAt(camera.position);
 | 
				
			||||
    var id = hotspot.name; | 
				
			||||
    // Check if hotspot is intersected with the picking ray.
 | 
				
			||||
    var intersects = this.raycaster.intersectObjects(hotspot.children); | 
				
			||||
    var isIntersected = (intersects.length > 0); | 
				
			||||
 | 
				
			||||
    // If newly selected, emit a focus event.
 | 
				
			||||
    if (isIntersected && !this.selectedHotspots[id]) { | 
				
			||||
      this.emit('focus', id); | 
				
			||||
      this.focus_(id); | 
				
			||||
    } | 
				
			||||
    // If no longer selected, emit a blur event.
 | 
				
			||||
    if (!isIntersected && this.selectedHotspots[id]) { | 
				
			||||
      this.emit('blur', id); | 
				
			||||
      this.blur_(id); | 
				
			||||
    } | 
				
			||||
    // Update the set of selected hotspots.
 | 
				
			||||
    if (isIntersected) { | 
				
			||||
      this.selectedHotspots[id] = true; | 
				
			||||
    } else { | 
				
			||||
      delete this.selectedHotspots[id]; | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Toggle whether or not hotspots are visible. | 
				
			||||
 */ | 
				
			||||
HotspotRenderer.prototype.setVisibility = function(isVisible) { | 
				
			||||
  this.hotspotRoot.visible = isVisible; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.onTouchStart_ = function(e) { | 
				
			||||
  // In VR mode, don't touch the pointer position.
 | 
				
			||||
  if (!this.worldRenderer.isVRMode()) { | 
				
			||||
    this.updateTouch_(e); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Force a camera update to see if any hotspots were selected.
 | 
				
			||||
  this.update(this.worldRenderer.camera); | 
				
			||||
 | 
				
			||||
  this.downHotspots = {}; | 
				
			||||
  for (var id in this.selectedHotspots) { | 
				
			||||
    this.downHotspots[id] = true; | 
				
			||||
    this.down_(id); | 
				
			||||
  } | 
				
			||||
  return false; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.onTouchEnd_ = function(e) { | 
				
			||||
  // If no hotspots are pressed, emit an empty click event.
 | 
				
			||||
  if (Util.isEmptyObject(this.downHotspots)) { | 
				
			||||
    this.emit('click'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Only emit a click if the finger was down on the same hotspot before.
 | 
				
			||||
  for (var id in this.downHotspots) { | 
				
			||||
    this.emit('click', id); | 
				
			||||
    this.up_(id); | 
				
			||||
    e.preventDefault(); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.updateTouch_ = function(e) { | 
				
			||||
  var size = this.getSize_(); | 
				
			||||
  var touch = e.touches[0]; | 
				
			||||
	this.pointer.x = (touch.clientX / size.width) * 2 - 1; | 
				
			||||
	this.pointer.y = - (touch.clientY / size.height) * 2 + 1; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.onMouseDown_ = function(e) { | 
				
			||||
  this.updateMouse_(e); | 
				
			||||
 | 
				
			||||
  this.downHotspots = {}; | 
				
			||||
  for (var id in this.selectedHotspots) { | 
				
			||||
    this.downHotspots[id] = true; | 
				
			||||
    this.down_(id); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.onMouseMove_ = function(e) { | 
				
			||||
  this.updateMouse_(e); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.onMouseUp_ = function(e) { | 
				
			||||
  this.updateMouse_(e); | 
				
			||||
 | 
				
			||||
  // If no hotspots are pressed, emit an empty click event.
 | 
				
			||||
  if (Util.isEmptyObject(this.downHotspots)) { | 
				
			||||
    this.emit('click'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Only emit a click if the mouse was down on the same hotspot before.
 | 
				
			||||
  for (var id in this.selectedHotspots) { | 
				
			||||
    if (id in this.downHotspots) { | 
				
			||||
      this.emit('click', id); | 
				
			||||
      this.up_(id); | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.updateMouse_ = function(e) { | 
				
			||||
  var size = this.getSize_(); | 
				
			||||
	this.pointer.x = (e.clientX / size.width) * 2 - 1; | 
				
			||||
	this.pointer.y = - (e.clientY / size.height) * 2 + 1; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.getSize_ = function() { | 
				
			||||
  var canvas = this.worldRenderer.renderer.domElement; | 
				
			||||
  return this.worldRenderer.renderer.getSize(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.createHotspot_ = function(radius, distance) { | 
				
			||||
  var innerGeometry = new THREE.CircleGeometry(radius, 32); | 
				
			||||
 | 
				
			||||
  var innerMaterial = new THREE.MeshBasicMaterial({ | 
				
			||||
    color: 0xffffff, side: THREE.DoubleSide, transparent: true, | 
				
			||||
    opacity: MAX_INNER_OPACITY, depthTest: false | 
				
			||||
  }); | 
				
			||||
 | 
				
			||||
  var inner = new THREE.Mesh(innerGeometry, innerMaterial); | 
				
			||||
  inner.name = 'inner'; | 
				
			||||
 | 
				
			||||
  var outerMaterial = new THREE.MeshBasicMaterial({ | 
				
			||||
    color: 0xffffff, side: THREE.DoubleSide, transparent: true, | 
				
			||||
    opacity: MAX_OUTER_OPACITY, depthTest: false | 
				
			||||
  }); | 
				
			||||
  var outerGeometry = new THREE.RingGeometry(radius * 0.85, radius, 32); | 
				
			||||
  var outer = new THREE.Mesh(outerGeometry, outerMaterial); | 
				
			||||
  outer.name = 'outer'; | 
				
			||||
 | 
				
			||||
  // Position at the extreme end of the sphere.
 | 
				
			||||
  var hotspot = new THREE.Object3D(); | 
				
			||||
  hotspot.position.z = -distance; | 
				
			||||
  hotspot.scale.copy(NORMAL_SCALE); | 
				
			||||
 | 
				
			||||
  hotspot.add(inner); | 
				
			||||
  hotspot.add(outer); | 
				
			||||
 | 
				
			||||
  return hotspot; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Large aspect ratios tend to cause visually jarring distortions on the sides. | 
				
			||||
 * Here we fade hotspots out to avoid them. | 
				
			||||
 */ | 
				
			||||
HotspotRenderer.prototype.fadeOffCenterHotspots_ = function(camera) { | 
				
			||||
  var lookAt = new THREE.Vector3(1, 0, 0); | 
				
			||||
  lookAt.applyQuaternion(camera.quaternion); | 
				
			||||
  // Take into account the camera parent too.
 | 
				
			||||
  lookAt.applyQuaternion(camera.parent.quaternion); | 
				
			||||
 | 
				
			||||
  // Go through each hotspot. Calculate how far off center it is.
 | 
				
			||||
  for (var id in this.hotspots) { | 
				
			||||
    var hotspot = this.hotspots[id]; | 
				
			||||
    var angle = hotspot.position.angleTo(lookAt); | 
				
			||||
    var angleDeg = THREE.Math.radToDeg(angle); | 
				
			||||
    var isVisible = angleDeg < 45; | 
				
			||||
    var opacity; | 
				
			||||
    if (angleDeg < FADE_START_ANGLE_DEG) { | 
				
			||||
      opacity = 1; | 
				
			||||
    } else if (angleDeg > FADE_END_ANGLE_DEG) { | 
				
			||||
      opacity = 0; | 
				
			||||
    } else { | 
				
			||||
      // We are in the case START < angle < END. Linearly interpolate.
 | 
				
			||||
      var range = FADE_END_ANGLE_DEG - FADE_START_ANGLE_DEG; | 
				
			||||
      var value = FADE_END_ANGLE_DEG - angleDeg; | 
				
			||||
      opacity = value / range; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Opacity a function of angle. If angle is large, opacity is zero. At some
 | 
				
			||||
    // point, ramp opacity down.
 | 
				
			||||
    this.setOpacity_(id, opacity); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.focus_ = function(id) { | 
				
			||||
  var hotspot = this.hotspots[id]; | 
				
			||||
 | 
				
			||||
  // Tween scale of hotspot.
 | 
				
			||||
  this.tween = new TWEEN.Tween(hotspot.scale).to(FOCUS_SCALE, FOCUS_DURATION) | 
				
			||||
      .easing(TWEEN.Easing.Quadratic.InOut) | 
				
			||||
      .start(); | 
				
			||||
  
 | 
				
			||||
  if (this.worldRenderer.isVRMode()) { | 
				
			||||
    this.timeForHospotClick = setTimeout(function () { | 
				
			||||
      this.emit('click', id); | 
				
			||||
    }, 1200 ) | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.blur_ = function(id) { | 
				
			||||
  var hotspot = this.hotspots[id]; | 
				
			||||
 | 
				
			||||
  this.tween = new TWEEN.Tween(hotspot.scale).to(NORMAL_SCALE, FOCUS_DURATION) | 
				
			||||
      .easing(TWEEN.Easing.Quadratic.InOut) | 
				
			||||
      .start(); | 
				
			||||
  
 | 
				
			||||
  if (this.timeForHospotClick) { | 
				
			||||
    clearTimeout( this.timeForHospotClick ); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.down_ = function(id) { | 
				
			||||
  // Become active.
 | 
				
			||||
  var hotspot = this.hotspots[id]; | 
				
			||||
  var outer = hotspot.getObjectByName('inner'); | 
				
			||||
 | 
				
			||||
  this.tween = new TWEEN.Tween(outer.material.color).to(ACTIVE_COLOR, ACTIVE_DURATION) | 
				
			||||
      .start(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.up_ = function(id) { | 
				
			||||
  // Become inactive.
 | 
				
			||||
  var hotspot = this.hotspots[id]; | 
				
			||||
  var outer = hotspot.getObjectByName('inner'); | 
				
			||||
 | 
				
			||||
  this.tween = new TWEEN.Tween(outer.material.color).to(INACTIVE_COLOR, ACTIVE_DURATION) | 
				
			||||
      .start(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
HotspotRenderer.prototype.setOpacity_ = function(id, opacity) { | 
				
			||||
  var hotspot = this.hotspots[id]; | 
				
			||||
  var outer = hotspot.getObjectByName('outer'); | 
				
			||||
  var inner = hotspot.getObjectByName('inner'); | 
				
			||||
 | 
				
			||||
  outer.material.opacity = opacity * MAX_OUTER_OPACITY; | 
				
			||||
  inner.material.opacity = opacity * MAX_INNER_OPACITY; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = HotspotRenderer; | 
				
			||||
@ -0,0 +1,68 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
var EventEmitter = require('eventemitter3'); | 
				
			||||
var Message = require('../message'); | 
				
			||||
var Util = require('../util'); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sits in an embedded iframe, receiving messages from a containing | 
				
			||||
 * iFrame. This facilitates an API which provides the following features: | 
				
			||||
 * | 
				
			||||
 *    Playing and pausing content. | 
				
			||||
 *    Adding hotspots. | 
				
			||||
 *    Sending messages back to the containing iframe when hotspot is clicked | 
				
			||||
 *    Sending analytics events to containing iframe. | 
				
			||||
 * | 
				
			||||
 * Note: this script used to also respond to synthetic devicemotion events, but | 
				
			||||
 * no longer does so. This is because as of iOS 9.2, Safari disallows listening | 
				
			||||
 * for devicemotion events within cross-device iframes. To work around this, the | 
				
			||||
 * webvr-polyfill responds to the postMessage event containing devicemotion | 
				
			||||
 * information (sent by the iframe-message-sender in the VR View API). | 
				
			||||
 */ | 
				
			||||
function IFrameMessageReceiver() { | 
				
			||||
  window.addEventListener('message', this.onMessage_.bind(this), false); | 
				
			||||
} | 
				
			||||
IFrameMessageReceiver.prototype = new EventEmitter(); | 
				
			||||
 | 
				
			||||
IFrameMessageReceiver.prototype.onMessage_ = function(event) { | 
				
			||||
  if (Util.isDebug()) { | 
				
			||||
    console.log('onMessage_', event); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  var message = event.data; | 
				
			||||
  var type = message.type.toLowerCase(); | 
				
			||||
  var data = message.data; | 
				
			||||
 | 
				
			||||
  switch (type) { | 
				
			||||
    case Message.SET_CONTENT: | 
				
			||||
    case Message.SET_VOLUME: | 
				
			||||
    case Message.MUTED: | 
				
			||||
    case Message.ADD_HOTSPOT: | 
				
			||||
    case Message.PLAY: | 
				
			||||
    case Message.PAUSE: | 
				
			||||
    case Message.SET_CURRENT_TIME: | 
				
			||||
    case Message.GET_POSITION: | 
				
			||||
    case Message.SET_FULLSCREEN: | 
				
			||||
      this.emit(type, data); | 
				
			||||
      break; | 
				
			||||
    default: | 
				
			||||
      if (Util.isDebug()) { | 
				
			||||
        console.warn('Got unknown message of type %s from %s', message.type, message.origin); | 
				
			||||
      } | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = IFrameMessageReceiver; | 
				
			||||
@ -0,0 +1,54 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Shows a 2D loading indicator while various pieces of EmbedVR load. | 
				
			||||
 */ | 
				
			||||
function LoadingIndicator() { | 
				
			||||
  this.el = this.build_(); | 
				
			||||
  document.body.appendChild(this.el); | 
				
			||||
  this.show(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
LoadingIndicator.prototype.build_ = function() { | 
				
			||||
  var overlay = document.createElement('div'); | 
				
			||||
  var s = overlay.style; | 
				
			||||
  s.position = 'fixed'; | 
				
			||||
  s.top = 0; | 
				
			||||
  s.left = 0; | 
				
			||||
  s.width = '100%'; | 
				
			||||
  s.height = '100%'; | 
				
			||||
  s.background = '#eee'; | 
				
			||||
  var img = document.createElement('img'); | 
				
			||||
  img.src = 'images/loading.gif'; | 
				
			||||
  var s = img.style; | 
				
			||||
  s.position = 'absolute'; | 
				
			||||
  s.top = '50%'; | 
				
			||||
  s.left = '50%'; | 
				
			||||
  s.transform = 'translate(-50%, -50%)'; | 
				
			||||
 | 
				
			||||
  overlay.appendChild(img); | 
				
			||||
  return overlay; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
LoadingIndicator.prototype.hide = function() { | 
				
			||||
  this.el.style.display = 'none'; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
LoadingIndicator.prototype.show = function() { | 
				
			||||
  this.el.style.display = 'block'; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = LoadingIndicator; | 
				
			||||
@ -0,0 +1,369 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
// Initialize the loading indicator as quickly as possible to give the user
 | 
				
			||||
// immediate feedback.
 | 
				
			||||
var LoadingIndicator = require('./loading-indicator'); | 
				
			||||
var loadIndicator = new LoadingIndicator(); | 
				
			||||
 | 
				
			||||
var ES6Promise = require('es6-promise'); | 
				
			||||
// Polyfill ES6 promises for IE.
 | 
				
			||||
ES6Promise.polyfill(); | 
				
			||||
 | 
				
			||||
var IFrameMessageReceiver = require('./iframe-message-receiver'); | 
				
			||||
var Message = require('../message'); | 
				
			||||
var SceneInfo = require('./scene-info'); | 
				
			||||
var Stats = require('../../node_modules/stats-js/build/stats.min'); | 
				
			||||
var Util = require('../util'); | 
				
			||||
var WebVRPolyfill = require('webvr-polyfill'); | 
				
			||||
var WorldRenderer = require('./world-renderer'); | 
				
			||||
 | 
				
			||||
var receiver = new IFrameMessageReceiver(); | 
				
			||||
receiver.on(Message.PLAY, onPlayRequest); | 
				
			||||
receiver.on(Message.PAUSE, onPauseRequest); | 
				
			||||
receiver.on(Message.ADD_HOTSPOT, onAddHotspot); | 
				
			||||
receiver.on(Message.SET_CONTENT, onSetContent); | 
				
			||||
receiver.on(Message.SET_VOLUME, onSetVolume); | 
				
			||||
receiver.on(Message.MUTED, onMuted); | 
				
			||||
receiver.on(Message.SET_CURRENT_TIME, onUpdateCurrentTime); | 
				
			||||
receiver.on(Message.GET_POSITION, onGetPosition); | 
				
			||||
receiver.on(Message.SET_FULLSCREEN, onSetFullscreen); | 
				
			||||
 | 
				
			||||
window.addEventListener('load', onLoad); | 
				
			||||
 | 
				
			||||
var stats = new Stats(); | 
				
			||||
var scene = SceneInfo.loadFromGetParams(); | 
				
			||||
 | 
				
			||||
var worldRenderer = new WorldRenderer(scene); | 
				
			||||
worldRenderer.on('error', onRenderError); | 
				
			||||
worldRenderer.on('load', onRenderLoad); | 
				
			||||
worldRenderer.on('modechange', onModeChange); | 
				
			||||
worldRenderer.on('ended', onEnded); | 
				
			||||
worldRenderer.on('play', onPlay); | 
				
			||||
worldRenderer.hotspotRenderer.on('click', onHotspotClick); | 
				
			||||
 | 
				
			||||
window.worldRenderer = worldRenderer; | 
				
			||||
 | 
				
			||||
var isReadySent = false; | 
				
			||||
var volume = 0; | 
				
			||||
 | 
				
			||||
function onLoad() { | 
				
			||||
  if (!Util.isWebGLEnabled()) { | 
				
			||||
    showError('WebGL not supported.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Load the scene.
 | 
				
			||||
  worldRenderer.setScene(scene); | 
				
			||||
 | 
				
			||||
  if (scene.isDebug) { | 
				
			||||
    // Show stats.
 | 
				
			||||
    showStats(); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (scene.isYawOnly) { | 
				
			||||
    WebVRConfig = window.WebVRConfig || {}; | 
				
			||||
    WebVRConfig.YAW_ONLY = true; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  requestAnimationFrame(loop); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
function onVideoTap() { | 
				
			||||
  worldRenderer.videoProxy.play(); | 
				
			||||
  hidePlayButton(); | 
				
			||||
 | 
				
			||||
  // Prevent multiple play() calls on the video element.
 | 
				
			||||
  document.body.removeEventListener('touchend', onVideoTap); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onRenderLoad(event) { | 
				
			||||
  if (event.videoElement) { | 
				
			||||
 | 
				
			||||
    var scene = SceneInfo.loadFromGetParams(); | 
				
			||||
 | 
				
			||||
    // On mobile, tell the user they need to tap to start. Otherwise, autoplay.
 | 
				
			||||
    if (Util.isMobile()) { | 
				
			||||
      // Tell user to tap to start.
 | 
				
			||||
      showPlayButton(); | 
				
			||||
      document.body.addEventListener('touchend', onVideoTap); | 
				
			||||
    } else { | 
				
			||||
      event.videoElement.play(); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    // Attach to pause and play events, to notify the API.
 | 
				
			||||
    event.videoElement.addEventListener('pause', onPause); | 
				
			||||
    event.videoElement.addEventListener('play', onPlay); | 
				
			||||
    event.videoElement.addEventListener('timeupdate', onGetCurrentTime); | 
				
			||||
    event.videoElement.addEventListener('ended', onEnded); | 
				
			||||
  } | 
				
			||||
  // Hide loading indicator.
 | 
				
			||||
  loadIndicator.hide(); | 
				
			||||
 | 
				
			||||
  // Autopan only on desktop, for photos only, and only if autopan is enabled.
 | 
				
			||||
  if (!Util.isMobile() && !worldRenderer.sceneInfo.video && !worldRenderer.sceneInfo.isAutopanOff) { | 
				
			||||
    worldRenderer.autopan(); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Notify the API that we are ready, but only do this once.
 | 
				
			||||
  if (!isReadySent) { | 
				
			||||
    if (event.videoElement) { | 
				
			||||
      Util.sendParentMessage({ | 
				
			||||
        type: 'ready', | 
				
			||||
        data: { | 
				
			||||
          duration: event.videoElement.duration | 
				
			||||
        } | 
				
			||||
      }); | 
				
			||||
    } else { | 
				
			||||
      Util.sendParentMessage({ | 
				
			||||
        type: 'ready' | 
				
			||||
      }); | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    isReadySent = true; | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onPlayRequest() { | 
				
			||||
  if (!worldRenderer.videoProxy) { | 
				
			||||
    onApiError('Attempt to pause, but no video found.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  worldRenderer.videoProxy.play(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onPauseRequest() { | 
				
			||||
  if (!worldRenderer.videoProxy) { | 
				
			||||
    onApiError('Attempt to pause, but no video found.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  worldRenderer.videoProxy.pause(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onAddHotspot(e) { | 
				
			||||
  if (Util.isDebug()) { | 
				
			||||
    console.log('onAddHotspot', e); | 
				
			||||
  } | 
				
			||||
  // TODO: Implement some validation?
 | 
				
			||||
 | 
				
			||||
  var pitch = parseFloat(e.pitch); | 
				
			||||
  var yaw = parseFloat(e.yaw); | 
				
			||||
  var radius = parseFloat(e.radius); | 
				
			||||
  var distance = parseFloat(e.distance); | 
				
			||||
  var id = e.id; | 
				
			||||
  worldRenderer.hotspotRenderer.add(pitch, yaw, radius, distance, id); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onSetContent(e) { | 
				
			||||
  if (Util.isDebug()) { | 
				
			||||
    console.log('onSetContent', e); | 
				
			||||
  } | 
				
			||||
  // Remove all of the hotspots.
 | 
				
			||||
  worldRenderer.hotspotRenderer.clearAll(); | 
				
			||||
  // Fade to black.
 | 
				
			||||
  worldRenderer.sphereRenderer.setOpacity(0, 500).then(function() { | 
				
			||||
    // Then load the new scene.
 | 
				
			||||
    var scene = SceneInfo.loadFromAPIParams(e.contentInfo); | 
				
			||||
    worldRenderer.destroy(); | 
				
			||||
 | 
				
			||||
    // Update the URL to reflect the new scene. This is important particularily
 | 
				
			||||
    // on iOS where we use a fake fullscreen mode.
 | 
				
			||||
    var url = scene.getCurrentUrl(); | 
				
			||||
    //console.log('Updating url to be %s', url);
 | 
				
			||||
    window.history.pushState(null, 'VR View', url); | 
				
			||||
 | 
				
			||||
    // And set the new scene.
 | 
				
			||||
    return worldRenderer.setScene(scene); | 
				
			||||
  }).then(function() { | 
				
			||||
    // Then fade the scene back in.
 | 
				
			||||
    worldRenderer.sphereRenderer.setOpacity(1, 500); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onSetVolume(e) { | 
				
			||||
  // Only work for video. If there's no video, send back an error.
 | 
				
			||||
  if (!worldRenderer.videoProxy) { | 
				
			||||
    onApiError('Attempt to set volume, but no video found.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  worldRenderer.videoProxy.setVolume(e.volumeLevel); | 
				
			||||
  volume = e.volumeLevel; | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'volumechange', | 
				
			||||
    data: e.volumeLevel | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onMuted(e) { | 
				
			||||
  // Only work for video. If there's no video, send back an error.
 | 
				
			||||
  if (!worldRenderer.videoProxy) { | 
				
			||||
    onApiError('Attempt to mute, but no video found.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  worldRenderer.videoProxy.mute(e.muteState); | 
				
			||||
 | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'muted', | 
				
			||||
    data: e.muteState | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onUpdateCurrentTime(time) { | 
				
			||||
  if (!worldRenderer.videoProxy) { | 
				
			||||
    onApiError('Attempt to pause, but no video found.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  worldRenderer.videoProxy.setCurrentTime(time); | 
				
			||||
  onGetCurrentTime(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onGetCurrentTime() { | 
				
			||||
  var time = worldRenderer.videoProxy.getCurrentTime(); | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'timeupdate', | 
				
			||||
    data: time | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onSetFullscreen() { | 
				
			||||
  if (!worldRenderer.videoProxy) { | 
				
			||||
    onApiError('Attempt to set fullscreen, but no video found.'); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  worldRenderer.manager.onFSClick_(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onApiError(message) { | 
				
			||||
  console.error(message); | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'error', | 
				
			||||
    data: {message: message} | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onModeChange(mode) { | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'modechange', | 
				
			||||
    data: {mode: mode} | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onHotspotClick(id) { | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'click', | 
				
			||||
    data: {id: id} | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onPlay() { | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'paused', | 
				
			||||
    data: false | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onPause() { | 
				
			||||
  Util.sendParentMessage({ | 
				
			||||
    type: 'paused', | 
				
			||||
    data: true | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onEnded() { | 
				
			||||
    Util.sendParentMessage({ | 
				
			||||
      type: 'ended', | 
				
			||||
      data: true | 
				
			||||
    }); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onSceneError(message) { | 
				
			||||
  showError('Loader: ' + message); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function onRenderError(message) { | 
				
			||||
  showError('Render: ' + message); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function showError(message) { | 
				
			||||
  // Hide loading indicator.
 | 
				
			||||
  loadIndicator.hide(); | 
				
			||||
 | 
				
			||||
  // Sanitize `message` as it could contain user supplied
 | 
				
			||||
  // values. Re-add the space character as to not modify the
 | 
				
			||||
  // error messages used throughout the codebase.
 | 
				
			||||
  message = encodeURI(message).replace(/%20/g, ' '); | 
				
			||||
 | 
				
			||||
  var error = document.querySelector('#error'); | 
				
			||||
  error.classList.add('visible'); | 
				
			||||
  error.querySelector('.message').innerHTML = message; | 
				
			||||
 | 
				
			||||
  error.querySelector('.title').innerHTML = 'Error'; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function hideError() { | 
				
			||||
  var error = document.querySelector('#error'); | 
				
			||||
  error.classList.remove('visible'); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function showPlayButton() { | 
				
			||||
  var playButton = document.querySelector('#play-overlay'); | 
				
			||||
  playButton.classList.add('visible'); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function hidePlayButton() { | 
				
			||||
  var playButton = document.querySelector('#play-overlay'); | 
				
			||||
  playButton.classList.remove('visible'); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function showStats() { | 
				
			||||
  stats.setMode(0); // 0: fps, 1: ms
 | 
				
			||||
 | 
				
			||||
  // Align bottom-left.
 | 
				
			||||
  stats.domElement.style.position = 'absolute'; | 
				
			||||
  stats.domElement.style.left = '0px'; | 
				
			||||
  stats.domElement.style.bottom = '0px'; | 
				
			||||
  document.body.appendChild(stats.domElement); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
function loop(time) { | 
				
			||||
  // Use the VRDisplay RAF if it is present.
 | 
				
			||||
  if (worldRenderer.vrDisplay) { | 
				
			||||
    worldRenderer.vrDisplay.requestAnimationFrame(loop); | 
				
			||||
  } else { | 
				
			||||
    requestAnimationFrame(loop); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  stats.begin(); | 
				
			||||
  // Update the video if needed.
 | 
				
			||||
  if (worldRenderer.videoProxy) { | 
				
			||||
    worldRenderer.videoProxy.update(time); | 
				
			||||
  } | 
				
			||||
  worldRenderer.render(time); | 
				
			||||
  worldRenderer.submitFrame(); | 
				
			||||
  stats.end(); | 
				
			||||
} | 
				
			||||
function onGetPosition() { | 
				
			||||
    Util.sendParentMessage({ | 
				
			||||
        type: 'getposition', | 
				
			||||
        data: { | 
				
			||||
            Yaw: worldRenderer.camera.rotation.y * 180 / Math.PI, | 
				
			||||
            Pitch: worldRenderer.camera.rotation.x * 180 / Math.PI | 
				
			||||
        } | 
				
			||||
    }); | 
				
			||||
} | 
				
			||||
@ -0,0 +1,41 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
function ReticleRenderer(camera) { | 
				
			||||
  this.camera = camera; | 
				
			||||
 | 
				
			||||
  this.reticle = this.createReticle_(); | 
				
			||||
  // In front of the hotspot itself, which is at r=0.99.
 | 
				
			||||
  this.reticle.position.z = -0.97; | 
				
			||||
  camera.add(this.reticle); | 
				
			||||
 | 
				
			||||
  this.setVisibility(false); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
ReticleRenderer.prototype.setVisibility = function(isVisible) { | 
				
			||||
  // TODO: Tween the transition.
 | 
				
			||||
  this.reticle.visible = isVisible; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
ReticleRenderer.prototype.createReticle_ = function() { | 
				
			||||
  // Make a torus.
 | 
				
			||||
  var geometry = new THREE.TorusGeometry(0.02, 0.005, 10, 20); | 
				
			||||
  var material = new THREE.MeshBasicMaterial({color: 0x000000}); | 
				
			||||
  var torus = new THREE.Mesh(geometry, material); | 
				
			||||
 | 
				
			||||
  return torus; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = ReticleRenderer; | 
				
			||||
@ -0,0 +1,125 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var Util = require('../util'); | 
				
			||||
 | 
				
			||||
var CAMEL_TO_UNDERSCORE = { | 
				
			||||
  video: 'video', | 
				
			||||
  image: 'image', | 
				
			||||
  preview: 'preview', | 
				
			||||
  loop: 'loop', | 
				
			||||
  volume: 'volume', | 
				
			||||
  muted: 'muted', | 
				
			||||
  isStereo: 'is_stereo', | 
				
			||||
  defaultYaw: 'default_yaw', | 
				
			||||
  isYawOnly: 'is_yaw_only', | 
				
			||||
  isDebug: 'is_debug', | 
				
			||||
  isVROff: 'is_vr_off', | 
				
			||||
  isAutopanOff: 'is_autopan_off', | 
				
			||||
  hideFullscreenButton: 'hide_fullscreen_button' | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Contains all information about a given scene. | 
				
			||||
 */ | 
				
			||||
function SceneInfo(opt_params) { | 
				
			||||
  var params = opt_params || {}; | 
				
			||||
  params.player = { | 
				
			||||
    loop: opt_params.loop, | 
				
			||||
    volume: opt_params.volume, | 
				
			||||
    muted: opt_params.muted | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
  this.image = params.image !== undefined ? encodeURI(params.image) : undefined; | 
				
			||||
  this.preview = params.preview !== undefined ? encodeURI(params.preview) : undefined; | 
				
			||||
  this.video = params.video !== undefined ? encodeURI(params.video) : undefined; | 
				
			||||
  this.defaultYaw = THREE.Math.degToRad(params.defaultYaw || 0); | 
				
			||||
 | 
				
			||||
  this.isStereo = Util.parseBoolean(params.isStereo); | 
				
			||||
  this.isYawOnly = Util.parseBoolean(params.isYawOnly); | 
				
			||||
  this.isDebug = Util.parseBoolean(params.isDebug); | 
				
			||||
  this.isVROff = Util.parseBoolean(params.isVROff); | 
				
			||||
  this.isAutopanOff = Util.parseBoolean(params.isAutopanOff); | 
				
			||||
  this.loop = Util.parseBoolean(params.player.loop); | 
				
			||||
  this.volume = parseFloat( | 
				
			||||
      params.player.volume ? params.player.volume : '1'); | 
				
			||||
  this.muted = Util.parseBoolean(params.player.muted); | 
				
			||||
  this.hideFullscreenButton = Util.parseBoolean(params.hideFullscreenButton); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
SceneInfo.loadFromGetParams = function() { | 
				
			||||
  var params = {}; | 
				
			||||
  for (var camelCase in CAMEL_TO_UNDERSCORE) { | 
				
			||||
    var underscore = CAMEL_TO_UNDERSCORE[camelCase]; | 
				
			||||
    params[camelCase] = Util.getQueryParameter(underscore) | 
				
			||||
                        || ((window.WebVRConfig && window.WebVRConfig.PLAYER) ? window.WebVRConfig.PLAYER[underscore] : ""); | 
				
			||||
  } | 
				
			||||
  var scene = new SceneInfo(params); | 
				
			||||
  if (!scene.isValid()) { | 
				
			||||
    console.warn('Invalid scene: %s', scene.errorMessage); | 
				
			||||
  } | 
				
			||||
  return scene; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
SceneInfo.loadFromAPIParams = function(underscoreParams) { | 
				
			||||
  var params = {}; | 
				
			||||
  for (var camelCase in CAMEL_TO_UNDERSCORE) { | 
				
			||||
    var underscore = CAMEL_TO_UNDERSCORE[camelCase]; | 
				
			||||
    if (underscoreParams[underscore]) { | 
				
			||||
      params[camelCase] = underscoreParams[underscore]; | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  var scene = new SceneInfo(params); | 
				
			||||
  if (!scene.isValid()) { | 
				
			||||
    console.warn('Invalid scene: %s', scene.errorMessage); | 
				
			||||
  } | 
				
			||||
  return scene; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
SceneInfo.prototype.isValid = function() { | 
				
			||||
  // Either it's an image or a video.
 | 
				
			||||
  if (!this.image && !this.video) { | 
				
			||||
    this.errorMessage = 'Either image or video URL must be specified.'; | 
				
			||||
    return false; | 
				
			||||
  } | 
				
			||||
  if (this.image && !this.isValidImage_(this.image)) { | 
				
			||||
    this.errorMessage = 'Invalid image URL: ' + this.image; | 
				
			||||
    return false; | 
				
			||||
  } | 
				
			||||
  this.errorMessage = null; | 
				
			||||
  return true; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Generates a URL to reflect this scene. | 
				
			||||
 */ | 
				
			||||
SceneInfo.prototype.getCurrentUrl = function() { | 
				
			||||
  var url = location.protocol + '//' + location.host + location.pathname + '?'; | 
				
			||||
  for (var camelCase in CAMEL_TO_UNDERSCORE) { | 
				
			||||
    var underscore = CAMEL_TO_UNDERSCORE[camelCase]; | 
				
			||||
    var value = this[camelCase]; | 
				
			||||
    if (value !== undefined) { | 
				
			||||
      url += underscore + '=' + value + '&'; | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  // Chop off the trailing ampersand.
 | 
				
			||||
  return url.substring(0, url.length - 1); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
SceneInfo.prototype.isValidImage_ = function(imageUrl) { | 
				
			||||
  return true; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = SceneInfo; | 
				
			||||
@ -0,0 +1,205 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var Eyes = require('./eyes'); | 
				
			||||
var TWEEN = require('@tweenjs/tween.js'); | 
				
			||||
var Util = require('../util'); | 
				
			||||
var VideoType = require('../video-type'); | 
				
			||||
 | 
				
			||||
function SphereRenderer(scene) { | 
				
			||||
  this.scene = scene; | 
				
			||||
 | 
				
			||||
  // Create a transparent mask.
 | 
				
			||||
  this.createOpacityMask_(); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sets the photosphere based on the image in the source. Supports stereo and | 
				
			||||
 * mono photospheres. | 
				
			||||
 * | 
				
			||||
 * @return {Promise} | 
				
			||||
 */ | 
				
			||||
SphereRenderer.prototype.setPhotosphere = function(src, opt_params) { | 
				
			||||
  return new Promise(function(resolve, reject) { | 
				
			||||
    this.resolve = resolve; | 
				
			||||
    this.reject = reject; | 
				
			||||
 | 
				
			||||
    var params = opt_params || {}; | 
				
			||||
 | 
				
			||||
    this.isStereo = !!params.isStereo; | 
				
			||||
    this.src = src; | 
				
			||||
 | 
				
			||||
    // Load texture.
 | 
				
			||||
    var loader = new THREE.TextureLoader(); | 
				
			||||
    loader.crossOrigin = 'anonymous'; | 
				
			||||
    loader.load(src, this.onTextureLoaded_.bind(this), undefined, | 
				
			||||
                this.onTextureError_.bind(this)); | 
				
			||||
  }.bind(this)); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @return {Promise} Yeah. | 
				
			||||
 */ | 
				
			||||
SphereRenderer.prototype.set360Video = function (videoElement, videoType, opt_params) { | 
				
			||||
  return new Promise(function(resolve, reject) { | 
				
			||||
    this.resolve = resolve; | 
				
			||||
    this.reject = reject; | 
				
			||||
 | 
				
			||||
    var params = opt_params || {}; | 
				
			||||
 | 
				
			||||
    this.isStereo = !!params.isStereo; | 
				
			||||
 | 
				
			||||
    // Load the video texture.
 | 
				
			||||
    var videoTexture = new THREE.VideoTexture(videoElement); | 
				
			||||
    videoTexture.minFilter = THREE.LinearFilter; | 
				
			||||
    videoTexture.magFilter = THREE.LinearFilter; | 
				
			||||
    videoTexture.generateMipmaps = false; | 
				
			||||
 | 
				
			||||
    if (Util.isSafari() && videoType === VideoType.HLS) { | 
				
			||||
      // fix black screen issue on safari
 | 
				
			||||
      videoTexture.format = THREE.RGBAFormat; | 
				
			||||
      videoTexture.flipY = false; | 
				
			||||
    } else { | 
				
			||||
      videoTexture.format = THREE.RGBFormat; | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    videoTexture.needsUpdate = true; | 
				
			||||
 | 
				
			||||
    this.onTextureLoaded_(videoTexture); | 
				
			||||
  }.bind(this)); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Set the opacity of the panorama. | 
				
			||||
 * | 
				
			||||
 * @param {Number} opacity How opaque we want the panorama to be. 0 means black, | 
				
			||||
 * 1 means full color. | 
				
			||||
 * @param {Number} duration Number of milliseconds the transition should take. | 
				
			||||
 * | 
				
			||||
 * @return {Promise} When the opacity change is complete. | 
				
			||||
 */ | 
				
			||||
SphereRenderer.prototype.setOpacity = function(opacity, duration) { | 
				
			||||
  var scene = this.scene; | 
				
			||||
  // If we want the opacity
 | 
				
			||||
  var overlayOpacity = 1 - opacity; | 
				
			||||
  return new Promise(function(resolve, reject) { | 
				
			||||
    var mask = scene.getObjectByName('opacityMask'); | 
				
			||||
    var tween = new TWEEN.Tween({opacity: mask.material.opacity}) | 
				
			||||
        .to({opacity: overlayOpacity}, duration) | 
				
			||||
        .easing(TWEEN.Easing.Quadratic.InOut); | 
				
			||||
    tween.onUpdate(function(e) { | 
				
			||||
      mask.material.opacity = this.opacity; | 
				
			||||
    }); | 
				
			||||
    tween.onComplete(resolve).start(); | 
				
			||||
  }); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
SphereRenderer.prototype.onTextureLoaded_ = function(texture) { | 
				
			||||
  var sphereLeft; | 
				
			||||
  var sphereRight; | 
				
			||||
  if (this.isStereo) { | 
				
			||||
    sphereLeft = this.createPhotosphere_(texture, {offsetY: 0.5, scaleY: 0.5}); | 
				
			||||
    sphereRight = this.createPhotosphere_(texture, {offsetY: 0, scaleY: 0.5}); | 
				
			||||
  } else { | 
				
			||||
    sphereLeft = this.createPhotosphere_(texture); | 
				
			||||
    sphereRight = this.createPhotosphere_(texture); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Display in left and right eye respectively.
 | 
				
			||||
  sphereLeft.layers.set(Eyes.LEFT); | 
				
			||||
  sphereLeft.eye = Eyes.LEFT; | 
				
			||||
  sphereLeft.name = 'eyeLeft'; | 
				
			||||
  sphereRight.layers.set(Eyes.RIGHT); | 
				
			||||
  sphereRight.eye = Eyes.RIGHT; | 
				
			||||
  sphereRight.name = 'eyeRight'; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
    this.scene.getObjectByName('photo').children = [sphereLeft, sphereRight]; | 
				
			||||
 | 
				
			||||
  this.resolve(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
SphereRenderer.prototype.onTextureError_ = function(error) { | 
				
			||||
  this.reject('Unable to load texture from "' + this.src + '"'); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
SphereRenderer.prototype.createPhotosphere_ = function(texture, opt_params) { | 
				
			||||
  var p = opt_params || {}; | 
				
			||||
  p.scaleX = p.scaleX || 1; | 
				
			||||
  p.scaleY = p.scaleY || 1; | 
				
			||||
  p.offsetX = p.offsetX || 0; | 
				
			||||
  p.offsetY = p.offsetY || 0; | 
				
			||||
  p.phiStart = p.phiStart || 0; | 
				
			||||
  p.phiLength = p.phiLength || Math.PI * 2; | 
				
			||||
  p.thetaStart = p.thetaStart || 0; | 
				
			||||
  p.thetaLength = p.thetaLength || Math.PI; | 
				
			||||
 | 
				
			||||
  var geometry = new THREE.SphereGeometry(1, 48, 48, | 
				
			||||
      p.phiStart, p.phiLength, p.thetaStart, p.thetaLength); | 
				
			||||
  geometry.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1)); | 
				
			||||
  var uvs = geometry.faceVertexUvs[0]; | 
				
			||||
  for (var i = 0; i < uvs.length; i ++) { | 
				
			||||
    for (var j = 0; j < 3; j ++) { | 
				
			||||
      uvs[i][j].x *= p.scaleX; | 
				
			||||
      uvs[i][j].x += p.offsetX; | 
				
			||||
      uvs[i][j].y *= p.scaleY; | 
				
			||||
      uvs[i][j].y += p.offsetY; | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  var material; | 
				
			||||
  if (texture.format === THREE.RGBAFormat && texture.flipY === false) { | 
				
			||||
    material = new THREE.ShaderMaterial({ | 
				
			||||
      uniforms: { | 
				
			||||
        texture: { value: texture } | 
				
			||||
      }, | 
				
			||||
      vertexShader: [ | 
				
			||||
        "varying vec2 vUV;", | 
				
			||||
        "void main() {", | 
				
			||||
        "	vUV = vec2( uv.x, 1.0 - uv.y );", | 
				
			||||
        "	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", | 
				
			||||
        "}" | 
				
			||||
      ].join("\n"), | 
				
			||||
      fragmentShader: [ | 
				
			||||
        "uniform sampler2D texture;", | 
				
			||||
        "varying vec2 vUV;", | 
				
			||||
        "void main() {", | 
				
			||||
        " gl_FragColor = texture2D( texture, vUV  )" + (Util.isIOS() ? ".bgra" : "") + ";", | 
				
			||||
        "}" | 
				
			||||
      ].join("\n") | 
				
			||||
    }); | 
				
			||||
  } else { | 
				
			||||
    material = new THREE.MeshBasicMaterial({ map: texture }); | 
				
			||||
  } | 
				
			||||
  var out = new THREE.Mesh(geometry, material); | 
				
			||||
  //out.visible = false;
 | 
				
			||||
  out.renderOrder = -1; | 
				
			||||
  return out; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
SphereRenderer.prototype.createOpacityMask_ = function() { | 
				
			||||
  var geometry = new THREE.SphereGeometry(0.49, 48, 48); | 
				
			||||
  var material = new THREE.MeshBasicMaterial({ | 
				
			||||
    color: 0x000000, side: THREE.DoubleSide, opacity: 0, transparent: true}); | 
				
			||||
  var opacityMask = new THREE.Mesh(geometry, material); | 
				
			||||
  opacityMask.name = 'opacityMask'; | 
				
			||||
  opacityMask.renderOrder = 1; | 
				
			||||
 | 
				
			||||
  this.scene.add(opacityMask); | 
				
			||||
  return opacityMask; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = SphereRenderer; | 
				
			||||
@ -0,0 +1,130 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var Util = require('../util'); | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * A proxy class for working around the fact that as soon as a video is play()ed | 
				
			||||
 * on iOS, Safari auto-fullscreens the video. | 
				
			||||
 * | 
				
			||||
 * TODO(smus): The entire raison d'etre for this class is to work around this | 
				
			||||
 * issue. Once Safari implements some way to suppress this fullscreen player, we | 
				
			||||
 * can remove this code. | 
				
			||||
 */ | 
				
			||||
function VideoProxy(videoElement) { | 
				
			||||
  this.videoElement = videoElement; | 
				
			||||
  // True if we're currently manually advancing the playhead (only on iOS).
 | 
				
			||||
  this.isFakePlayback = false; | 
				
			||||
 | 
				
			||||
  // When the video started playing.
 | 
				
			||||
  this.startTime = null; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
VideoProxy.prototype.play = function() { | 
				
			||||
  if (Util.isIOS9OrLess()) { | 
				
			||||
    this.startTime = performance.now(); | 
				
			||||
    this.isFakePlayback = true; | 
				
			||||
 | 
				
			||||
    // Make an audio element to playback just the audio part.
 | 
				
			||||
    this.audioElement = new Audio(); | 
				
			||||
    this.audioElement.src = this.videoElement.src; | 
				
			||||
    this.audioElement.play(); | 
				
			||||
  } else { | 
				
			||||
    this.videoElement.play().then(function(e) { | 
				
			||||
      console.log('Playing video.', e); | 
				
			||||
    }); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
VideoProxy.prototype.pause = function() { | 
				
			||||
  if (Util.isIOS9OrLess() && this.isFakePlayback) { | 
				
			||||
    this.isFakePlayback = true; | 
				
			||||
 | 
				
			||||
    this.audioElement.pause(); | 
				
			||||
  } else { | 
				
			||||
    this.videoElement.pause(); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
VideoProxy.prototype.setVolume = function(volumeLevel) { | 
				
			||||
  if (this.videoElement) { | 
				
			||||
    // On iOS 10, the VideoElement.volume property is read-only. So we special
 | 
				
			||||
    // case muting and unmuting.
 | 
				
			||||
    if (Util.isIOS()) { | 
				
			||||
      this.videoElement.muted = (volumeLevel === 0); | 
				
			||||
    } else { | 
				
			||||
      this.videoElement.volume = volumeLevel; | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  if (this.audioElement) { | 
				
			||||
    this.audioElement.volume = volumeLevel; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Set the attribute mute of the elements according with the muteState param. | 
				
			||||
 * | 
				
			||||
 * @param bool muteState | 
				
			||||
 */ | 
				
			||||
VideoProxy.prototype.mute = function(muteState) { | 
				
			||||
  if (this.videoElement) { | 
				
			||||
    this.videoElement.muted = muteState; | 
				
			||||
  } | 
				
			||||
  if (this.audioElement) { | 
				
			||||
    this.audioElement.muted = muteState; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
VideoProxy.prototype.getCurrentTime = function() { | 
				
			||||
  return Util.isIOS9OrLess() ? this.audioElement.currentTime : this.videoElement.currentTime; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * | 
				
			||||
 * @param {Object} time | 
				
			||||
 */ | 
				
			||||
VideoProxy.prototype.setCurrentTime = function(time) { | 
				
			||||
  if (this.videoElement) { | 
				
			||||
    this.videoElement.currentTime = time.currentTime; | 
				
			||||
  } | 
				
			||||
  if (this.audioElement) { | 
				
			||||
    this.audioElement.currentTime = time.currentTime; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Called on RAF to progress playback. | 
				
			||||
 */ | 
				
			||||
VideoProxy.prototype.update = function() { | 
				
			||||
  // Fakes playback for iOS only.
 | 
				
			||||
  if (!this.isFakePlayback) { | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
  var duration = this.videoElement.duration; | 
				
			||||
  var now = performance.now(); | 
				
			||||
  var delta = now - this.startTime; | 
				
			||||
  var deltaS = delta / 1000; | 
				
			||||
  this.videoElement.currentTime = deltaS; | 
				
			||||
 | 
				
			||||
  // Loop through the video
 | 
				
			||||
  if (deltaS > duration) { | 
				
			||||
    this.startTime = now; | 
				
			||||
    this.videoElement.currentTime = 0; | 
				
			||||
    // Also restart the audio.
 | 
				
			||||
    this.audioElement.currentTime = 0; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = VideoProxy; | 
				
			||||
@ -0,0 +1,20 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
// Load EmbedVR.
 | 
				
			||||
require('./main'); | 
				
			||||
 | 
				
			||||
// Load Analytics for EmbedVR.
 | 
				
			||||
require('./analytics'); | 
				
			||||
@ -0,0 +1,372 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
var AdaptivePlayer = require('./adaptive-player'); | 
				
			||||
var EventEmitter = require('eventemitter3'); | 
				
			||||
var Eyes = require('./eyes'); | 
				
			||||
var HotspotRenderer = require('./hotspot-renderer'); | 
				
			||||
var ReticleRenderer = require('./reticle-renderer'); | 
				
			||||
var SphereRenderer = require('./sphere-renderer'); | 
				
			||||
var TWEEN = require('@tweenjs/tween.js'); | 
				
			||||
var Util = require('../util'); | 
				
			||||
var VideoProxy = require('./video-proxy'); | 
				
			||||
var WebVRManager = require('webvr-boilerplate'); | 
				
			||||
 | 
				
			||||
var AUTOPAN_DURATION = 3000; | 
				
			||||
var AUTOPAN_ANGLE = 0.4; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * The main WebGL rendering entry point. Manages the scene, camera, VR-related | 
				
			||||
 * rendering updates. Interacts with the WebVRManager. | 
				
			||||
 * | 
				
			||||
 * Coordinates the other renderers: SphereRenderer, HotspotRenderer, | 
				
			||||
 * ReticleRenderer. | 
				
			||||
 * | 
				
			||||
 * Also manages the AdaptivePlayer and VideoProxy. | 
				
			||||
 * | 
				
			||||
 * Emits the following events: | 
				
			||||
 *   load: when the scene is loaded. | 
				
			||||
 *   error: if there is an error loading the scene. | 
				
			||||
 *   modechange(Boolean isVR): if the mode (eg. VR, fullscreen, etc) changes. | 
				
			||||
 */ | 
				
			||||
function WorldRenderer(params) { | 
				
			||||
  this.init_(params.hideFullscreenButton); | 
				
			||||
 | 
				
			||||
  this.sphereRenderer = new SphereRenderer(this.scene); | 
				
			||||
  this.hotspotRenderer = new HotspotRenderer(this); | 
				
			||||
  this.hotspotRenderer.on('focus', this.onHotspotFocus_.bind(this)); | 
				
			||||
  this.hotspotRenderer.on('blur', this.onHotspotBlur_.bind(this)); | 
				
			||||
  this.reticleRenderer = new ReticleRenderer(this.camera); | 
				
			||||
 | 
				
			||||
  // Get the VR Display as soon as we initialize.
 | 
				
			||||
  navigator.getVRDisplays().then(function(displays) { | 
				
			||||
    if (displays.length > 0) { | 
				
			||||
      this.vrDisplay = displays[0]; | 
				
			||||
    } | 
				
			||||
  }.bind(this)); | 
				
			||||
 | 
				
			||||
} | 
				
			||||
WorldRenderer.prototype = new EventEmitter(); | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.render = function(time) { | 
				
			||||
  this.controls.update(); | 
				
			||||
  TWEEN.update(time); | 
				
			||||
  this.effect.render(this.scene, this.camera); | 
				
			||||
  this.hotspotRenderer.update(this.camera); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @return {Promise} When the scene is fully loaded. | 
				
			||||
 */ | 
				
			||||
WorldRenderer.prototype.setScene = function(scene) { | 
				
			||||
  var self = this; | 
				
			||||
  var promise = new Promise(function(resolve, reject) { | 
				
			||||
    self.sceneResolve = resolve; | 
				
			||||
    self.sceneReject = reject; | 
				
			||||
  }); | 
				
			||||
 | 
				
			||||
  if (!scene || !scene.isValid()) { | 
				
			||||
    this.didLoadFail_(scene.errorMessage); | 
				
			||||
    return; | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  var params = { | 
				
			||||
    isStereo: scene.isStereo, | 
				
			||||
    loop: scene.loop, | 
				
			||||
    volume: scene.volume, | 
				
			||||
    muted: scene.muted | 
				
			||||
  }; | 
				
			||||
 | 
				
			||||
  this.setDefaultYaw_(scene.defaultYaw || 0); | 
				
			||||
 | 
				
			||||
  // Disable VR mode if explicitly disabled, or if we're loading a video on iOS
 | 
				
			||||
  // 9 or earlier.
 | 
				
			||||
  if (scene.isVROff || (scene.video && Util.isIOS9OrLess())) { | 
				
			||||
    this.manager.setVRCompatibleOverride(false); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Set various callback overrides in iOS.
 | 
				
			||||
  if (Util.isIOS()) { | 
				
			||||
    this.manager.setFullscreenCallback(function() { | 
				
			||||
      Util.sendParentMessage({type: 'enter-fullscreen'}); | 
				
			||||
    }); | 
				
			||||
    this.manager.setExitFullscreenCallback(function() { | 
				
			||||
      Util.sendParentMessage({type: 'exit-fullscreen'}); | 
				
			||||
    }); | 
				
			||||
    this.manager.setVRCallback(function() { | 
				
			||||
      Util.sendParentMessage({type: 'enter-vr'}); | 
				
			||||
    }); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // If we're dealing with an image, and not a video.
 | 
				
			||||
  if (scene.image && !scene.video) { | 
				
			||||
    if (scene.preview) { | 
				
			||||
      // First load the preview.
 | 
				
			||||
      this.sphereRenderer.setPhotosphere(scene.preview, params).then(function() { | 
				
			||||
        // As soon as something is loaded, emit the load event to hide the
 | 
				
			||||
        // loading progress bar.
 | 
				
			||||
        self.didLoad_(); | 
				
			||||
        // Then load the full resolution image.
 | 
				
			||||
        self.sphereRenderer.setPhotosphere(scene.image, params); | 
				
			||||
      }).catch(self.didLoadFail_.bind(self)); | 
				
			||||
    } else { | 
				
			||||
      // No preview -- go straight to rendering the full image.
 | 
				
			||||
      this.sphereRenderer.setPhotosphere(scene.image, params).then(function() { | 
				
			||||
        self.didLoad_(); | 
				
			||||
      }).catch(self.didLoadFail_.bind(self)); | 
				
			||||
    } | 
				
			||||
  } else if (scene.video) { | 
				
			||||
    if (Util.isIE11()) { | 
				
			||||
      // On IE 11, if an 'image' param is provided, load it instead of showing
 | 
				
			||||
      // an error.
 | 
				
			||||
      //
 | 
				
			||||
      // TODO(smus): Once video textures are supported, remove this fallback.
 | 
				
			||||
      if (scene.image) { | 
				
			||||
        this.sphereRenderer.setPhotosphere(scene.image, params).then(function() { | 
				
			||||
          self.didLoad_(); | 
				
			||||
        }).catch(self.didLoadFail_.bind(self)); | 
				
			||||
      } else { | 
				
			||||
        this.didLoadFail_('Video is not supported on IE11.'); | 
				
			||||
      } | 
				
			||||
    } else { | 
				
			||||
      this.player = new AdaptivePlayer(params); | 
				
			||||
      this.player.on('load', function(videoElement, videoType) { | 
				
			||||
        self.sphereRenderer.set360Video(videoElement, videoType, params).then(function() { | 
				
			||||
          self.didLoad_({videoElement: videoElement}); | 
				
			||||
        }).catch(self.didLoadFail_.bind(self)); | 
				
			||||
      }); | 
				
			||||
      this.player.on('error', function(error) { | 
				
			||||
        self.didLoadFail_('Video load error: ' + error); | 
				
			||||
      }); | 
				
			||||
      this.player.load(scene.video); | 
				
			||||
 | 
				
			||||
      this.videoProxy = new VideoProxy(this.player.video); | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  this.sceneInfo = scene; | 
				
			||||
  if (Util.isDebug()) { | 
				
			||||
    console.log('Loaded scene', scene); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  return promise; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.isVRMode = function() { | 
				
			||||
  return !!this.vrDisplay && this.vrDisplay.isPresenting; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.submitFrame = function() { | 
				
			||||
  if (this.isVRMode()) { | 
				
			||||
    this.vrDisplay.submitFrame(); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.disposeEye_ = function(eye) { | 
				
			||||
  if (eye) { | 
				
			||||
    if (eye.material.map) { | 
				
			||||
      eye.material.map.dispose(); | 
				
			||||
    } | 
				
			||||
    eye.material.dispose(); | 
				
			||||
    eye.geometry.dispose(); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.dispose = function() { | 
				
			||||
  var eyeLeft = this.scene.getObjectByName('eyeLeft'); | 
				
			||||
  this.disposeEye_(eyeLeft); | 
				
			||||
  var eyeRight = this.scene.getObjectByName('eyeRight'); | 
				
			||||
  this.disposeEye_(eyeRight); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.destroy = function() { | 
				
			||||
  if (this.player) { | 
				
			||||
    this.player.removeAllListeners(); | 
				
			||||
    this.player.destroy(); | 
				
			||||
    this.player = null; | 
				
			||||
  } | 
				
			||||
  var photo = this.scene.getObjectByName('photo'); | 
				
			||||
  var eyeLeft = this.scene.getObjectByName('eyeLeft'); | 
				
			||||
  var eyeRight = this.scene.getObjectByName('eyeRight'); | 
				
			||||
 | 
				
			||||
  if (eyeLeft) { | 
				
			||||
    this.disposeEye_(eyeLeft); | 
				
			||||
    photo.remove(eyeLeft); | 
				
			||||
    this.scene.remove(eyeLeft); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  if (eyeRight) { | 
				
			||||
    this.disposeEye_(eyeRight); | 
				
			||||
    photo.remove(eyeRight); | 
				
			||||
    this.scene.remove(eyeRight); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.didLoad_ = function(opt_event) { | 
				
			||||
  var event = opt_event || {}; | 
				
			||||
  this.emit('load', event); | 
				
			||||
  if (this.sceneResolve) { | 
				
			||||
    this.sceneResolve(); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.didLoadFail_ = function(message) { | 
				
			||||
  this.emit('error', message); | 
				
			||||
  if (this.sceneReject) { | 
				
			||||
    this.sceneReject(message); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Sets the default yaw. | 
				
			||||
 * @param {Number} angleRad The yaw in radians. | 
				
			||||
 */ | 
				
			||||
WorldRenderer.prototype.setDefaultYaw_ = function(angleRad) { | 
				
			||||
  // Rotate the camera parent to take into account the scene's rotation.
 | 
				
			||||
  // By default, it should be at the center of the image.
 | 
				
			||||
  var display = this.controls.getVRDisplay(); | 
				
			||||
  // For desktop, we subtract the current display Y axis
 | 
				
			||||
  var theta = display.theta_ || 0; | 
				
			||||
  // For devices with orientation we make the current view center
 | 
				
			||||
  if (display.poseSensor_) { | 
				
			||||
    display.poseSensor_.resetPose(); | 
				
			||||
  } | 
				
			||||
  this.camera.parent.rotation.y = (Math.PI / 2.0) + angleRad - theta; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Do the initial camera tween to rotate the camera, giving an indication that | 
				
			||||
 * there is live content there (on desktop only). | 
				
			||||
 */ | 
				
			||||
WorldRenderer.prototype.autopan = function(duration) { | 
				
			||||
  var targetY = this.camera.parent.rotation.y - AUTOPAN_ANGLE; | 
				
			||||
  var tween = new TWEEN.Tween(this.camera.parent.rotation) | 
				
			||||
      .to({y: targetY}, AUTOPAN_DURATION) | 
				
			||||
      .easing(TWEEN.Easing.Quadratic.Out) | 
				
			||||
      .start(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.init_ = function(hideFullscreenButton) { | 
				
			||||
  var container = document.querySelector('body'); | 
				
			||||
  var aspect = window.innerWidth / window.innerHeight; | 
				
			||||
  var camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 100); | 
				
			||||
  camera.layers.enable(1); | 
				
			||||
 | 
				
			||||
  var cameraDummy = new THREE.Object3D(); | 
				
			||||
  cameraDummy.add(camera); | 
				
			||||
 | 
				
			||||
  // Antialiasing disabled to improve performance.
 | 
				
			||||
  var renderer = new THREE.WebGLRenderer({antialias: false}); | 
				
			||||
  renderer.setClearColor(0x000000, 0); | 
				
			||||
  renderer.setSize(window.innerWidth, window.innerHeight); | 
				
			||||
  renderer.setPixelRatio(window.devicePixelRatio); | 
				
			||||
 | 
				
			||||
  container.appendChild(renderer.domElement); | 
				
			||||
 | 
				
			||||
  var controls = new THREE.VRControls(camera); | 
				
			||||
  var effect = new THREE.VREffect(renderer); | 
				
			||||
 | 
				
			||||
  // Disable eye separation.
 | 
				
			||||
  effect.scale = 0; | 
				
			||||
  effect.setSize(window.innerWidth, window.innerHeight); | 
				
			||||
 | 
				
			||||
  // Present submission of frames automatically. This is done manually in
 | 
				
			||||
  // submitFrame().
 | 
				
			||||
  effect.autoSubmitFrame = false; | 
				
			||||
 | 
				
			||||
  this.camera = camera; | 
				
			||||
  this.renderer = renderer; | 
				
			||||
  this.effect = effect; | 
				
			||||
  this.controls = controls; | 
				
			||||
  this.manager = new WebVRManager(renderer, effect, {predistorted: false, hideButton: hideFullscreenButton}); | 
				
			||||
 | 
				
			||||
  this.scene = this.createScene_(); | 
				
			||||
  this.scene.add(this.camera.parent); | 
				
			||||
 | 
				
			||||
 | 
				
			||||
  // Watch the resize event.
 | 
				
			||||
  window.addEventListener('resize', this.onResize_.bind(this)); | 
				
			||||
 | 
				
			||||
  // Prevent context menu.
 | 
				
			||||
  window.addEventListener('contextmenu', this.onContextMenu_.bind(this)); | 
				
			||||
 | 
				
			||||
  window.addEventListener('vrdisplaypresentchange', | 
				
			||||
                          this.onVRDisplayPresentChange_.bind(this)); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.onResize_ = function() { | 
				
			||||
  this.effect.setSize(window.innerWidth, window.innerHeight); | 
				
			||||
  this.camera.aspect = window.innerWidth / window.innerHeight; | 
				
			||||
  this.camera.updateProjectionMatrix(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.onVRDisplayPresentChange_ = function(e) { | 
				
			||||
  if (Util.isDebug()) { | 
				
			||||
    console.log('onVRDisplayPresentChange_'); | 
				
			||||
  } | 
				
			||||
  var isVR = this.isVRMode(); | 
				
			||||
 | 
				
			||||
  // If the mode changed to VR and there is at least one hotspot, show reticle.
 | 
				
			||||
  var isReticleVisible = isVR && this.hotspotRenderer.getCount() > 0; | 
				
			||||
  this.reticleRenderer.setVisibility(isReticleVisible); | 
				
			||||
 | 
				
			||||
  // Resize the renderer for good measure.
 | 
				
			||||
  this.onResize_(); | 
				
			||||
 | 
				
			||||
  // Analytics.
 | 
				
			||||
  if (window.analytics) { | 
				
			||||
    analytics.logModeChanged(isVR); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // When exiting VR mode from iOS, make sure we emit back an exit-fullscreen event.
 | 
				
			||||
  if (!isVR && Util.isIOS()) { | 
				
			||||
    Util.sendParentMessage({type: 'exit-fullscreen'}); | 
				
			||||
  } | 
				
			||||
 | 
				
			||||
  // Emit a mode change event back to any listeners.
 | 
				
			||||
  this.emit('modechange', isVR); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.createScene_ = function(opt_params) { | 
				
			||||
  var scene = new THREE.Scene(); | 
				
			||||
 | 
				
			||||
  // Add a group for the photosphere.
 | 
				
			||||
  var photoGroup = new THREE.Object3D(); | 
				
			||||
  photoGroup.name = 'photo'; | 
				
			||||
  scene.add(photoGroup); | 
				
			||||
 | 
				
			||||
  return scene; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.onHotspotFocus_ = function(id) { | 
				
			||||
  // Set the default cursor to be a pointer.
 | 
				
			||||
  this.setCursor_('pointer'); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.onHotspotBlur_ = function(id) { | 
				
			||||
  // Reset the default cursor to be the default one.
 | 
				
			||||
  this.setCursor_(''); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.setCursor_ = function(cursor) { | 
				
			||||
  this.renderer.domElement.style.cursor = cursor; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
WorldRenderer.prototype.onContextMenu_ = function(e) { | 
				
			||||
  e.preventDefault(); | 
				
			||||
  e.stopPropagation(); | 
				
			||||
  return false; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = WorldRenderer; | 
				
			||||
@ -0,0 +1,33 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Messages from the API to the embed. | 
				
			||||
 */ | 
				
			||||
var Message = { | 
				
			||||
  PLAY: 'play', | 
				
			||||
  PAUSE: 'pause', | 
				
			||||
  TIMEUPDATE: 'timeupdate', | 
				
			||||
  ADD_HOTSPOT: 'addhotspot', | 
				
			||||
  SET_CONTENT: 'setimage', | 
				
			||||
  SET_VOLUME: 'setvolume', | 
				
			||||
  MUTED: 'muted', | 
				
			||||
  SET_CURRENT_TIME: 'setcurrenttime', | 
				
			||||
  DEVICE_MOTION: 'devicemotion', | 
				
			||||
  GET_POSITION: 'getposition', | 
				
			||||
  SET_FULLSCREEN: 'setfullscreen', | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = Message; | 
				
			||||
@ -0,0 +1,29 @@ | 
				
			||||
import '../../../node_modules/three/src/polyfills.js'; | 
				
			||||
 | 
				
			||||
export { WebGLRenderer } from '../../../node_modules/three/src/renderers/WebGLRenderer.js'; | 
				
			||||
export { Scene } from '../../../node_modules/three/src/scenes/Scene.js'; | 
				
			||||
export { Mesh } from '../../../node_modules/three/src/objects/Mesh.js'; | 
				
			||||
export { VideoTexture } from '../../../node_modules/three/src/textures/VideoTexture.js'; | 
				
			||||
export { MeshBasicMaterial } from '../../../node_modules/three/src/materials/MeshBasicMaterial.js'; | 
				
			||||
export { ShaderMaterial } from '../../../node_modules/three/src/materials/ShaderMaterial.js'; | 
				
			||||
export { TextureLoader } from '../../../node_modules/three/src/loaders/TextureLoader.js'; | 
				
			||||
export { PerspectiveCamera } from '../../../node_modules/three/src/cameras/PerspectiveCamera.js'; | 
				
			||||
export { Object3D } from '../../../node_modules/three/src/core/Object3D.js'; | 
				
			||||
export { Raycaster } from '../../../node_modules/three/src/core/Raycaster.js'; | 
				
			||||
export { _Math as Math } from '../../../node_modules/three/src/math/Math.js'; | 
				
			||||
export { Quaternion } from '../../../node_modules/three/src/math/Quaternion.js'; | 
				
			||||
export { Euler } from '../../../node_modules/three/src/math/Euler.js'; | 
				
			||||
export { Matrix4 } from '../../../node_modules/three/src/math/Matrix4.js'; | 
				
			||||
export { Matrix3 } from '../../../node_modules/three/src/math/Matrix3.js'; | 
				
			||||
export { Vector4 } from '../../../node_modules/three/src/math/Vector4.js'; | 
				
			||||
export { Vector3 } from '../../../node_modules/three/src/math/Vector3.js'; | 
				
			||||
export { Vector2 } from '../../../node_modules/three/src/math/Vector2.js'; | 
				
			||||
export { Color } from '../../../node_modules/three/src/math/Color.js'; | 
				
			||||
export { TorusGeometry } from '../../../node_modules/three/src/geometries/TorusGeometry.js'; | 
				
			||||
export { SphereGeometry } from '../../../node_modules/three/src/geometries/SphereGeometry.js'; | 
				
			||||
export { CircleGeometry } from '../../../node_modules/three/src/geometries/CircleGeometry.js'; | 
				
			||||
export { RingGeometry } from '../../../node_modules/three/src/geometries/RingGeometry.js'; | 
				
			||||
export * from '../../../node_modules/three/src/constants.js'; | 
				
			||||
 | 
				
			||||
import '../../../node_modules/three/examples/js/controls/VRControls.js'; | 
				
			||||
import '../../../node_modules/three/examples/js/effects/VREffect.js'; | 
				
			||||
									
										Binary file not shown.
									
								
							
						@ -0,0 +1,10 @@ | 
				
			||||
var THREE; | 
				
			||||
var define; | 
				
			||||
var module; | 
				
			||||
var exports; | 
				
			||||
var performance; | 
				
			||||
var WebGL2RenderingContext; | 
				
			||||
var VRDisplay; | 
				
			||||
var PositionSensorVRDevice; | 
				
			||||
var HMDVRDevice; | 
				
			||||
var VRFrameData; | 
				
			||||
@ -0,0 +1,34 @@ | 
				
			||||
import * as fs from 'fs'; | 
				
			||||
 | 
				
			||||
var outro = ` | 
				
			||||
Object.defineProperty( exports, 'AudioContext', { | 
				
			||||
	get: function () { | 
				
			||||
		return exports.getAudioContext(); | 
				
			||||
	} | 
				
			||||
});`;
 | 
				
			||||
 | 
				
			||||
function glsl () { | 
				
			||||
	return { | 
				
			||||
		transform ( code, id ) { | 
				
			||||
			if ( !/\.glsl$/.test( id ) ) return; | 
				
			||||
 | 
				
			||||
			return 'export default ' + JSON.stringify( | 
				
			||||
				code | 
				
			||||
					.replace( /[ \t]*\/\/.*\n/g, '' ) | 
				
			||||
					.replace( /[ \t]*\/\*[\s\S]*?\*\//g, '' ) | 
				
			||||
					.replace( /\n{2,}/g, '\n' ) | 
				
			||||
			) + ';'; | 
				
			||||
		} | 
				
			||||
	}; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
export default { | 
				
			||||
	entry: 'src/third_party/three/Three.js', | 
				
			||||
	dest: 'build/three.js', | 
				
			||||
	moduleName: 'THREE', | 
				
			||||
	format: 'umd', | 
				
			||||
	plugins: [ | 
				
			||||
		glsl() | 
				
			||||
	], | 
				
			||||
	outro: outro | 
				
			||||
}; | 
				
			||||
@ -0,0 +1,241 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
var Util = window.Util || {}; | 
				
			||||
 | 
				
			||||
Util.isDataURI = function(src) { | 
				
			||||
  return src && src.indexOf('data:') == 0; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.generateUUID = function() { | 
				
			||||
  function s4() { | 
				
			||||
    return Math.floor((1 + Math.random()) * 0x10000) | 
				
			||||
    .toString(16) | 
				
			||||
    .substring(1); | 
				
			||||
  } | 
				
			||||
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + | 
				
			||||
    s4() + '-' + s4() + s4() + s4(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isMobile = function() { | 
				
			||||
  var check = false; | 
				
			||||
  (function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera); | 
				
			||||
  return check; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isIOS = function() { | 
				
			||||
  return /(iPad|iPhone|iPod)/g.test(navigator.userAgent); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isSafari = function() { | 
				
			||||
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.cloneObject = function(obj) { | 
				
			||||
  var out = {}; | 
				
			||||
  for (key in obj) { | 
				
			||||
    out[key] = obj[key]; | 
				
			||||
  } | 
				
			||||
  return out; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.hashCode = function(s) { | 
				
			||||
  return s.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.loadTrackSrc = function(context, src, callback, opt_progressCallback) { | 
				
			||||
  var request = new XMLHttpRequest(); | 
				
			||||
  request.open('GET', src, true); | 
				
			||||
  request.responseType = 'arraybuffer'; | 
				
			||||
 | 
				
			||||
  // Decode asynchronously.
 | 
				
			||||
  request.onload = function() { | 
				
			||||
    context.decodeAudioData(request.response, function(buffer) { | 
				
			||||
      callback(buffer); | 
				
			||||
    }, function(e) { | 
				
			||||
      console.error(e); | 
				
			||||
    }); | 
				
			||||
  }; | 
				
			||||
  if (opt_progressCallback) { | 
				
			||||
    request.onprogress = function(e) { | 
				
			||||
      var percent = e.loaded / e.total; | 
				
			||||
      opt_progressCallback(percent); | 
				
			||||
    }; | 
				
			||||
  } | 
				
			||||
  request.send(); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isPow2 = function(n) { | 
				
			||||
  return (n & (n - 1)) == 0; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.capitalize = function(s) { | 
				
			||||
  return s.charAt(0).toUpperCase() + s.slice(1); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isIFrame = function() { | 
				
			||||
  try { | 
				
			||||
    return window.self !== window.top; | 
				
			||||
  } catch (e) { | 
				
			||||
    return true; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// From http://goo.gl/4WX3tg
 | 
				
			||||
Util.getQueryParameter = function(name) { | 
				
			||||
  name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); | 
				
			||||
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), | 
				
			||||
      results = regex.exec(location.search); | 
				
			||||
  return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
 | 
				
			||||
// From http://stackoverflow.com/questions/11871077/proper-way-to-detect-webgl-support.
 | 
				
			||||
Util.isWebGLEnabled = function() { | 
				
			||||
  var canvas = document.createElement('canvas'); | 
				
			||||
  try { gl = canvas.getContext("webgl"); } | 
				
			||||
  catch (x) { gl = null; } | 
				
			||||
 | 
				
			||||
  if (gl == null) { | 
				
			||||
    try { gl = canvas.getContext("experimental-webgl"); experimental = true; } | 
				
			||||
    catch (x) { gl = null; } | 
				
			||||
  } | 
				
			||||
  return !!gl; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.clone = function(obj) { | 
				
			||||
  return JSON.parse(JSON.stringify(obj)); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// From http://stackoverflow.com/questions/10140604/fastest-hypotenuse-in-javascript
 | 
				
			||||
Util.hypot = Math.hypot || function(x, y) { | 
				
			||||
  return Math.sqrt(x*x + y*y); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
// From http://stackoverflow.com/a/17447718/693934
 | 
				
			||||
Util.isIE11 = function() { | 
				
			||||
  return navigator.userAgent.match(/Trident/); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getRectCenter = function(rect) { | 
				
			||||
  return new THREE.Vector2(rect.x + rect.width/2, rect.y + rect.height/2); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getScreenWidth = function() { | 
				
			||||
  return Math.max(window.screen.width, window.screen.height) * | 
				
			||||
      window.devicePixelRatio; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getScreenHeight = function() { | 
				
			||||
  return Math.min(window.screen.width, window.screen.height) * | 
				
			||||
      window.devicePixelRatio; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isIOS9OrLess = function() { | 
				
			||||
  if (!Util.isIOS()) { | 
				
			||||
    return false; | 
				
			||||
  } | 
				
			||||
  var re = /(iPhone|iPad|iPod) OS ([\d_]+)/; | 
				
			||||
  var iOSVersion = navigator.userAgent.match(re); | 
				
			||||
  if (!iOSVersion) { | 
				
			||||
    return false; | 
				
			||||
  } | 
				
			||||
  // Get the last group.
 | 
				
			||||
  var versionString = iOSVersion[iOSVersion.length - 1]; | 
				
			||||
  var majorVersion = parseFloat(versionString); | 
				
			||||
  return majorVersion <= 9; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getExtension = function(url) { | 
				
			||||
  return url.split('.').pop().split('?')[0]; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.createGetParams = function(params) { | 
				
			||||
  var out = '?'; | 
				
			||||
  for (var k in params) { | 
				
			||||
    var paramString = k + '=' + params[k] + '&'; | 
				
			||||
    out += paramString; | 
				
			||||
  } | 
				
			||||
  // Remove the trailing ampersand.
 | 
				
			||||
  out.substring(0, params.length - 2); | 
				
			||||
  return out; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.sendParentMessage = function(message) { | 
				
			||||
  if (window.parent) { | 
				
			||||
    parent.postMessage(message, '*'); | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.parseBoolean = function(value) { | 
				
			||||
  if (value == 'false' || value == 0) { | 
				
			||||
    return false; | 
				
			||||
  } else if (value == 'true' || value == 1) { | 
				
			||||
    return true; | 
				
			||||
  } else { | 
				
			||||
    return !!value; | 
				
			||||
  } | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @param base {String} An absolute directory root. | 
				
			||||
 * @param relative {String} A relative path. | 
				
			||||
 * | 
				
			||||
 * @returns {String} An absolute path corresponding to the rootPath. | 
				
			||||
 * | 
				
			||||
 * From http://stackoverflow.com/a/14780463/693934.
 | 
				
			||||
 */ | 
				
			||||
Util.relativeToAbsolutePath = function(base, relative) { | 
				
			||||
  var stack = base.split('/'); | 
				
			||||
  var parts = relative.split('/'); | 
				
			||||
  for (var i = 0; i < parts.length; i++) { | 
				
			||||
    if (parts[i] == '.') { | 
				
			||||
      continue; | 
				
			||||
    } | 
				
			||||
    if (parts[i] == '..') { | 
				
			||||
      stack.pop(); | 
				
			||||
    } else { | 
				
			||||
      stack.push(parts[i]); | 
				
			||||
    } | 
				
			||||
  } | 
				
			||||
  return stack.join('/'); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * @return {Boolean} True iff the specified path is an absolute path. | 
				
			||||
 */ | 
				
			||||
Util.isPathAbsolute = function(path) { | 
				
			||||
  return ! /^(?:\/|[a-z]+:\/\/)/.test(path); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
Util.isEmptyObject = function(obj) { | 
				
			||||
  return Object.getOwnPropertyNames(obj).length == 0; | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.isDebug = function() { | 
				
			||||
  return Util.parseBoolean(Util.getQueryParameter('debug')); | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
Util.getCurrentScript = function() { | 
				
			||||
  // Note: in IE11, document.currentScript doesn't work, so we fall back to this
 | 
				
			||||
  // hack, taken from https://goo.gl/TpExuH.
 | 
				
			||||
  if (!document.currentScript) { | 
				
			||||
    console.warn('This browser does not support document.currentScript. Trying fallback.'); | 
				
			||||
  } | 
				
			||||
  return document.currentScript || document.scripts[document.scripts.length - 1]; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
module.exports = Util; | 
				
			||||
@ -0,0 +1,25 @@ | 
				
			||||
/* | 
				
			||||
 * Copyright 2016 Google Inc. All Rights Reserved. | 
				
			||||
 * Licensed under the Apache License, Version 2.0 (the "License"); | 
				
			||||
 * you may not use this file except in compliance with the License. | 
				
			||||
 * You may obtain a copy of the License at | 
				
			||||
 * | 
				
			||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||
 * | 
				
			||||
 * Unless required by applicable law or agreed to in writing, software | 
				
			||||
 * distributed under the License is distributed on an "AS IS" BASIS, | 
				
			||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
				
			||||
 * See the License for the specific language governing permissions and | 
				
			||||
 * limitations under the License. | 
				
			||||
 */ | 
				
			||||
 | 
				
			||||
/** | 
				
			||||
 * Video Types | 
				
			||||
 */ | 
				
			||||
var VideoTypes = { | 
				
			||||
  HLS: 1, | 
				
			||||
  DASH: 2, | 
				
			||||
  VIDEO: 3 | 
				
			||||
}; | 
				
			||||
 | 
				
			||||
module.exports = VideoTypes; | 
				
			||||
@ -0,0 +1,100 @@ | 
				
			||||
html, body { | 
				
			||||
  background-color: #000; | 
				
			||||
  color: #eee; | 
				
			||||
  margin: 0px; | 
				
			||||
  padding: 0px; | 
				
			||||
  position: fixed; | 
				
			||||
  overflow: hidden; | 
				
			||||
  top: 0; | 
				
			||||
  bottom: 0; | 
				
			||||
  left: 0; | 
				
			||||
  right: 0; | 
				
			||||
 | 
				
			||||
  /* Prevent user selection. */ | 
				
			||||
  user-select: none; | 
				
			||||
  touch-callout: none; | 
				
			||||
  -webkit-touch-callout: none; | 
				
			||||
  -webkit-user-select: none; | 
				
			||||
} | 
				
			||||
.dialog { | 
				
			||||
  display: none; | 
				
			||||
  align-items: center; | 
				
			||||
  justify-content: center; | 
				
			||||
  font-family: sans-serif; | 
				
			||||
  font-size: 170%; | 
				
			||||
  position: absolute; | 
				
			||||
  width: 100%; | 
				
			||||
  height: 100%; | 
				
			||||
  background: rgba(255, 255, 255, 0.2); | 
				
			||||
  -webkit-user-select: none; | 
				
			||||
  -moz-user-select: none; | 
				
			||||
  user-select: none; | 
				
			||||
} | 
				
			||||
.dialog.visible { | 
				
			||||
  display: flex; | 
				
			||||
} | 
				
			||||
.dialog .wrap { | 
				
			||||
  padding: 30px 60px; | 
				
			||||
  margin: 20px auto; | 
				
			||||
  width: 60%; | 
				
			||||
  background: rgba(0, 0, 0, 0.8); | 
				
			||||
  border-radius: 5px; | 
				
			||||
} | 
				
			||||
.dialog h1 { | 
				
			||||
  margin: 0; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
a, a:visited { | 
				
			||||
  color: skyblue; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
#watermark img { | 
				
			||||
  position: fixed; | 
				
			||||
  overflow: hidden; | 
				
			||||
  left: 0; | 
				
			||||
  bottom: 0; | 
				
			||||
  opacity: 0.3; | 
				
			||||
  width: 24px; | 
				
			||||
  height: 24px; | 
				
			||||
  padding: 12px; | 
				
			||||
  -webkit-user-select: none; | 
				
			||||
  -moz-user-select: none; | 
				
			||||
  user-select: none; | 
				
			||||
} | 
				
			||||
#watermark img:hover { | 
				
			||||
  opacity: 1; | 
				
			||||
  -webkit-filter: drop-shadow(white 0 0 5px); | 
				
			||||
} | 
				
			||||
 | 
				
			||||
 | 
				
			||||
canvas { | 
				
			||||
  cursor: -webkit-grab; | 
				
			||||
} | 
				
			||||
canvas:active { | 
				
			||||
  cursor: -webkit-grabbing; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
#play-overlay { | 
				
			||||
  position: fixed; | 
				
			||||
  width: 100%; | 
				
			||||
  height: 100%; | 
				
			||||
  left: 0; | 
				
			||||
  top: 0; | 
				
			||||
  right: 0; | 
				
			||||
  bottom: 0; | 
				
			||||
  background-color: rgba(0, 0, 0, 0.5); | 
				
			||||
  display: none; | 
				
			||||
	align-items: center; | 
				
			||||
  justify-content: center; | 
				
			||||
} | 
				
			||||
#play-overlay.visible { | 
				
			||||
	display: flex; | 
				
			||||
} | 
				
			||||
#play-overlay img { | 
				
			||||
  width: 30%; | 
				
			||||
  height: 30%; | 
				
			||||
} | 
				
			||||
 | 
				
			||||
#error .message { | 
				
			||||
  font-family: monospace; | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue