├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── demo
├── demo-elements.js
└── index.html
├── google-youtube.js
├── index.html
├── package-lock.json
├── package.json
└── test
├── google-youtube-basic.html
├── google-youtube-custom-thumbnail.html
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2014 Google Inc
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | https://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | google-youtube
2 | ==============
3 |
4 | See https://elements.polymer-project.org/elements/google-youtube
5 |
6 | Test
7 | ====
8 |
9 | ```
10 | polymer test --module-resolution=node --npm
11 | ```
--------------------------------------------------------------------------------
/demo/demo-elements.js:
--------------------------------------------------------------------------------
1 | import '@polymer/polymer/polymer-legacy.js';
2 | import '../google-youtube.js';
3 | import { Polymer } from '@polymer/polymer/lib/legacy/polymer-fn.js';
4 | const $_documentContainer = document.createElement('template');
5 |
6 | $_documentContainer.innerHTML = `
7 |
8 |
13 | <google-youtube>
Demo
14 | Full API Demo
15 |
30 |
31 |
32 |
33 |
34 | Playback Progress:
35 | {{currentTimeFormatted}}
36 | /
37 | {{durationFormatted}}
38 |
39 |
40 |
Volume: {{volume}}
41 |
Playback Rate: {{playbackRate}}
42 |
Playback Quality: {{playbackQuality}}
43 |
44 |
45 |
46 |
50 | Play
51 |
52 |
56 | Pause
57 |
58 |
59 |
60 |
61 | Video ID:
62 |
63 | Cue
64 |
65 |
66 |
67 |
Player Events:
68 |
69 |
70 | State change: {{item.data}}
71 |
72 |
73 |
74 |
75 | Custom Thumbnail Demo
76 |
81 |
82 |
83 | Playlist Demo
84 |
89 |
90 |
91 |
92 | `;
93 |
94 | document.head.appendChild($_documentContainer.content);
95 | Polymer({
96 | is: 'demo-element',
97 | properties: {
98 | playSupported: Boolean,
99 | state: Number,
100 | currentTime: Number,
101 | currentTimeFormatted: String,
102 | duration: Number,
103 | durationFormatted: String,
104 | fractionLoaded: Number,
105 | volume: Number,
106 | playbackRate: Number,
107 | playbackQuality: String,
108 | events: {
109 | type: Array,
110 | value: []
111 | }
112 | },
113 | computeProgress: function(currentTime, duration) {
114 | if (currentTime === undefined || duration === undefined) {
115 | return 0;
116 | }
117 |
118 | return currentTime / duration;
119 | },
120 | computePlayDisabled: function(state, playSupported) {
121 | return state == 1 || state == 3 || !playSupported;
122 | },
123 | computePauseDisabled: function(state) {
124 | return state != 1 && state != 3;
125 | },
126 | handleStateChange: function(ev) {
127 | this.events.push({data: ev.detail.data});
128 | },
129 | handleYouTubeError: function(ev) {
130 | console.error('YouTube playback error', ev.detail);
131 | },
132 | handlePlayVideo: function(ev) {
133 | this.$.googleYouTube.play();
134 | },
135 | handlePauseVideo: function(ev) {
136 | this.$.googleYouTube.pause();
137 | },
138 | handleCueVideo: function(ev) {
139 | this.$.googleYouTube.videoId = this.$.videoId.value;
140 | }
141 | });
142 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <google-youtube> Demo
8 |
9 |
10 |
11 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/google-youtube.js:
--------------------------------------------------------------------------------
1 | /**
2 | @license
3 | Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
4 | This code may only be used under the BSD style license found at https://polymer.github.io/LICENSE.txt
5 | The complete set of authors may be found at https://polymer.github.io/AUTHORS.txt
6 | The complete set of contributors may be found at https://polymer.github.io/CONTRIBUTORS.txt
7 | Code distributed by Google as part of the polymer project is also
8 | subject to an additional IP rights grant found at https://polymer.github.io/PATENTS.txt
9 | */
10 | import '@google-web-components/google-apis/google-youtube-api.js';
11 | import '@polymer/iron-localstorage/iron-localstorage.js';
12 |
13 | import { Polymer } from '@polymer/polymer/lib/legacy/polymer-fn.js';
14 | import { html } from '@polymer/polymer/lib/utils/html-tag.js';
15 |
16 | /**
17 | `google-youtube` encapsulates the YouTube player into a web component.
18 |
19 |
26 |
27 |
28 | `google-youtube` supports all of the [embedded player parameters](https://developers.google.com/youtube/player_parameters). Each can be set as an attribute on `google-youtube`.
29 |
30 | The standard set of [YouTube player events](https://developers.google.com/youtube/iframe_api_reference#Events) are exposed, as well as methods for playing, pausing, seeking to a specific time, and loading a new video.
31 |
32 |
33 | Custom property | Description | Default
34 | ----------------|-------------|----------
35 | `--google-youtube-container` | Mixin applied to the container div | `{}`
36 | `--google-youtube-thumbnail` | Mixin for the video thumbnail | `{}`
37 | `--google-youtube-iframe` | Mixin for the embeded iframe | `{}`
38 |
39 |
40 | @demo
41 | */
42 | Polymer({
43 | _template: html`
44 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
95 |
96 |
97 |
98 |
99 |
100 | `,
101 |
102 | is: 'google-youtube',
103 |
104 | /**
105 | * Fired when the YouTube player is fully initialized and ready for use.
106 | *
107 | * @event google-youtube-ready
108 | */
109 |
110 | /**
111 | * Fired when the state of the player changes. `e.detail.data` is set to one of
112 | * [the documented](https://developers.google.com/youtube/iframe_api_reference#onStateChange)
113 | * states.
114 | *
115 | * @event google-youtube-state-change
116 | */
117 |
118 | /**
119 | * Fired when playback fails due to an error. `e.detail.data` is set to one of
120 | * [the documented](https://developers.google.com/youtube/iframe_api_reference#onError)
121 | * error codes.
122 | *
123 | * @event google-youtube-error
124 | */
125 |
126 | properties: {
127 | /**
128 | * Sets the id of the video to play. Changing this attribute will trigger a call
129 | * to load a new video into the player (if `this.autoplay` is set to `1` and `playsupported` is true)
130 | * or cue a new video otherwise.
131 | *
132 | * The underlying YouTube embed will not be added to the page unless
133 | * `videoId` or `list` property is set.
134 | *
135 | * You can [search for videos programmatically](https://developers.google.com/youtube/v3/docs/search/list)
136 | * using the YouTube Data API, or just hardcode known video ids to display on your page.
137 | */
138 | videoId: {
139 | type: String,
140 | value: '',
141 | observer: '_videoIdChanged'
142 | },
143 |
144 | /**
145 | * The list parameter, in conjunction with the listType parameter, identifies the content that will load in the player.
146 | * If the listType parameter value is search, then the list parameter value specifies the search query.
147 | * If the listType parameter value is user_uploads, then the list parameter value identifies the YouTube channel whose uploaded videos will be loaded.
148 | * If the listType parameter value is playlist, then the list parameter value specifies a YouTube playlist ID. In the parameter value, you need to prepend the playlist ID with the letters PL as shown in the example below.
149 | *
150 | * See https://developers.google.com/youtube/player_parameters#list
151 | */
152 | list: {
153 | type: String,
154 | value: ''
155 | },
156 |
157 | /**
158 | * See https://developers.google.com/youtube/player_parameters#listtype
159 | */
160 | listType: String,
161 |
162 | /**
163 | * Decides whether YouTube API should be loaded.
164 | */
165 | shouldLoadApi: {
166 | type: Boolean,
167 | computed: '_computeShouldLoadApi(list, videoId)'
168 | },
169 |
170 | /**
171 | * Whether programmatic `.play()` for initial playback is supported in the current browser.
172 | *
173 | * Most mobile browsers [do not support](https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-SpecificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP40009523-CH5-SW1) autoplaying or scripted playback of videos.
174 | * If you attempt to automatically initiate playback of a ``, e.g. by calling the `play()` method before
175 | * playback has initially begun, the YouTube Player will enter an unrecoverable "stuck" state.
176 | * To protect against this, check the value of `playsupported` and don't call `play()` if it is set to `false`.
177 | * (You can hide/disable your custom play button, etc.)
178 | *
179 | * The `playsupported` value is determined at runtime, by dynamically creating a `` element with an
180 | * inlined data source and calling `play()` on it. (Inspired by [Modernizr](https://github.com/Modernizr/Modernizr/blob/master/feature-detects/video/autoplay.js).)
181 | *
182 | * If you would rather not incur the minimal overhead involved in going through this process, you can explicitly set
183 | * `playsupported` to `true` or `false` when initializing ``. This is only recommended if you know that
184 | * your web app will never (or only) be used on mobile browsers.
185 | */
186 | playsupported: {
187 | type: Boolean,
188 | value: null,
189 | notify: true
190 | },
191 |
192 | /**
193 | * "1" if video should start automatically
194 | */
195 | autoplay: {
196 | type: Number,
197 | value: 0
198 | },
199 | /**
200 | * Whether playback has started.
201 | *
202 | * This defaults to `false` and is set to `true` once the first 'playing' event is fired by
203 | * the underlying YouTube Player API.
204 | *
205 | * Once set to `true`, it will remain that way indefinitely.
206 | * Paused/buffering/ended events won't cause `playbackstarted` to reset to `false`.
207 | * Nor will loading a new video into the player.
208 | */
209 | playbackstarted: {
210 | type: Boolean,
211 | value: false,
212 | notify: true
213 | },
214 |
215 | /**
216 | * Sets the height of the player on the page.
217 | * Accepts anything valid for a CSS measurement, e.g. '200px' or '50%'.
218 | * If the unit of measurement is left off, 'px' is assumed.
219 | */
220 | height: {
221 | type: String,
222 | value: '270px'
223 | },
224 |
225 | /**
226 | * Sets the width of the player on the page.
227 | * Accepts anything valid for a CSS measurement, e.g. '200px' or '50%'.
228 | * If the unit of measurement is left off, 'px' is assumed.
229 | */
230 | width: {
231 | type: String,
232 | value:'480px'
233 | },
234 |
235 | /**
236 | * Exposes the current player state.
237 | * Using this attribute is an alternative to listening to `google-youtube-state-change` events,
238 | * and can simplify the logic in templates with conditional binding.
239 | *
240 | * The [possible values](https://developers.google.com/youtube/iframe_api_reference#onStateChange):
241 | * - -1 (unstarted)
242 | * - 0 (ended)
243 | * - 1 (playing)
244 | * - 2 (paused)
245 | * - 3 (buffering)
246 | * - 5 (video cued)
247 | */
248 | state: {
249 | type: Number,
250 | value: -1,
251 | notify: true
252 | },
253 |
254 | /**
255 | * Exposes the current playback time, in seconds.
256 | *
257 | * You can divide this value by the `duration` to determine the playback percentage.
258 | *
259 | * Default type is int. Setting `statsUpdateInterval` to less than a
260 | * second turns it into float to accommodate higher precision.
261 | */
262 | currenttime: {
263 | type: Number,
264 | value: 0,
265 | notify: true
266 | },
267 |
268 | /**
269 | * Exposes the video duration, in seconds.
270 | *
271 | * You can divide the `currenttime` to determine the playback percentage.
272 | */
273 | duration: {
274 | type: Number,
275 | value: 1, // To avoid divide-by-zero errors if used before video is cued.
276 | notify: true
277 | },
278 |
279 | /**
280 | * Exposes the current playback time, formatted as a (HH:)MM:SS string.
281 | */
282 | currenttimeformatted: {
283 | type: String,
284 | value: '0:00',
285 | notify: true
286 | },
287 |
288 | /**
289 | * Exposes the video duration, formatted as a (HH:)MM:SS string.
290 | */
291 | durationformatted: {
292 | type: String,
293 | value: '0:00', // To avoid divide-by-zero errors if used before video is cued.
294 | notify: true
295 | },
296 |
297 | /**
298 | * The fraction of the bytes that have been loaded for the current video, in the range [0-1].
299 | */
300 | fractionloaded: {
301 | type: Number,
302 | value: 0,
303 | notify: true
304 | },
305 |
306 | /**
307 | * A shorthand to enable a set of player attributes that, used together, simulate a "chromeless" YouTube player.
308 | *
309 | * Equivalent to setting the following attributes:
310 | * - `controls="0"`
311 | * - `modestbranding="1"`
312 | * - `showinfo="0"`
313 | * - `iv_load_policy="3"`
314 | * - `rel="0"`
315 | *
316 | * The "chromeless" player has minimal YouTube branding in cued state, and the native controls
317 | * will be disabled during playback. Creating your own custom play/pause/etc. controls is recommended.
318 | */
319 | chromeless: {
320 | type: Boolean,
321 | value: false
322 | },
323 | /**
324 | * The URL of an image to use as a custom thumbnail.
325 | *
326 | * This is optional; if not provided, the standard YouTube embed (which uses the thumbnail associated
327 | * with the video on YouTube) will be used.
328 | *
329 | * If `thumbnail` is set, than an ` ` containing the thumbnail will be used in lieu of the actual
330 | * YouTube embed. When the thumbnail is clicked, the ` ` is swapped out for the actual YouTube embed,
331 | * which will have [`autoplay=1`](https://developers.google.com/youtube/player_parameters#autoplay) set by default (in additional to any other player parameters specified on this element).
332 | *
333 | * Please note that `autoplay=1` won't actually autoplay videos on mobile browsers, so two taps will be required
334 | * to play the video there. Also, on desktop browsers, setting `autoplay=1` will prevent the playback
335 | * from [incrementing the view count](https://support.google.com/youtube/answer/1714329) for the video.
336 | */
337 | thumbnail: {
338 | type: String,
339 | value: ''
340 | },
341 |
342 | /**
343 | * If `fluid` is set, then the player will set its width to 100% to fill
344 | * the parent container, while adding `padding-top` to preserve the
345 | * aspect ratio provided by `width` and `height`. If `width` and `height`
346 | * have not been set, the player will fall back to a 16:9 aspect ratio.
347 | * This is useful for responsive designs where you don't want to
348 | * introduce letterboxing on your video.
349 | */
350 | fluid: {
351 | type: Boolean,
352 | value: false
353 | },
354 |
355 | /**
356 | * Returns the player's current volume, an integer between 0 and 100.
357 | * Note that `getVolume()` will return the volume even if the player is muted.
358 | */
359 | volume: {
360 | type: Number,
361 | value: 100,
362 | notify: true
363 | },
364 |
365 | /**
366 | * This function retrieves the playback rate of the currently playing video.
367 | * The default playback rate is 1, which indicates that the video is playing at normal speed.
368 | * Playback rates may include values like `0.25`, `0.5`, `1`, `1.5`, and `2`.
369 | */
370 | playbackrate: {
371 | type: Number,
372 | value: 1,
373 | notify: true
374 | },
375 |
376 | /**
377 | * This function retrieves the actual video quality of the current video.
378 | * Possible return values are `highres`, `hd1080`, `hd720`, `large`, `medium` and `small`.
379 | * It will also return `undefined` if there is no current video.
380 | */
381 | playbackquality: {
382 | type: String,
383 | value: '',
384 | notify: true
385 | },
386 |
387 | /**
388 | * Sets refresh interval in milliseconds for updating playback stats.
389 | * YouTube API does not send events for video progress so we have to
390 | * call getCurrentTime() manually. Smaller value makes updates smoother.
391 | *
392 | * When the value is less than 1 second, `currenttime` becomes float to
393 | * accommodate higher precision (default is int).
394 | */
395 | statsUpdateInterval: {
396 | type: Number,
397 | value: 1000,
398 | },
399 |
400 | },
401 |
402 | _computeContainerStyle: function(width, height) {
403 | return 'width:' + width + '; height:' + height;
404 | },
405 |
406 | _computeShouldLoadApi: function(videoId, list) {
407 | return Boolean(videoId || list);
408 | },
409 |
410 | _useExistingPlaySupportedValue: function() {
411 | this.playsupported = this._playsupportedLocalStorage;
412 | },
413 |
414 | /**
415 | * Detects whether programmatic .play() is supported in the current browser.
416 | *
417 | * This is triggered via on-ironlocalstorage-load-empty. The logic is:
418 | * - If playsupported is explicitly set to true or false on the element, use that.
419 | * - Otherwise, if there's a cached value in localStorage, use that.
420 | * - Otherwise, create a hidden element and call play() on it:
421 | * - If playback starts, playsupported is true.
422 | * - If playback doesn't start (within 500ms), playsupported is false.
423 | * - Whatever happens, cache the result in localStorage.
424 | */
425 | _determinePlaySupported: function() {
426 | // If playsupported isn't already being overridden by the page using this component,
427 | // then attempt to determine if it's supported.
428 | // This is deliberately checking with ==, to match either undefined or null.
429 | if (this.playsupported == null) {
430 | // Run a new playback test.
431 | var timeout;
432 | var videoElement = document.createElement('video');
433 |
434 | if ('play' in videoElement) {
435 | videoElement.id = 'playtest';
436 | // Some browsers will refuse to play videos with 'display: none' set,
437 | // so position the video well offscreen instead.
438 | // Modify the .style property directly instead of using CSS to work around polyfill
439 | // issues; see https://github.com/GoogleWebComponents/google-youtube/issues/49
440 | videoElement.style.position = 'absolute';
441 | videoElement.style.top = '-9999px';
442 | videoElement.style.left = '-9999px';
443 |
444 | var mp4Source = document.createElement('source');
445 | mp4Source.src = "data:video/mp4;base64,AAAAFGZ0eXBNU05WAAACAE1TTlYAAAOUbW9vdgAAAGxtdmhkAAAAAM9ghv7PYIb+AAACWAAACu8AAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAnh0cmFrAAAAXHRraGQAAAAHz2CG/s9ghv4AAAABAAAAAAAACu8AAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAFAAAAA4AAAAAAHgbWRpYQAAACBtZGhkAAAAAM9ghv7PYIb+AAALuAAANq8AAAAAAAAAIWhkbHIAAAAAbWhscnZpZGVBVlMgAAAAAAABAB4AAAABl21pbmYAAAAUdm1oZAAAAAAAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAVdzdGJsAAAAp3N0c2QAAAAAAAAAAQAAAJdhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAFAAOABIAAAASAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAAEmNvbHJuY2xjAAEAAQABAAAAL2F2Y0MBTUAz/+EAGGdNQDOadCk/LgIgAAADACAAAAMA0eMGVAEABGjuPIAAAAAYc3R0cwAAAAAAAAABAAAADgAAA+gAAAAUc3RzcwAAAAAAAAABAAAAAQAAABxzdHNjAAAAAAAAAAEAAAABAAAADgAAAAEAAABMc3RzegAAAAAAAAAAAAAADgAAAE8AAAAOAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA4AAAAOAAAAFHN0Y28AAAAAAAAAAQAAA7AAAAA0dXVpZFVTTVQh0k/Ou4hpXPrJx0AAAAAcTVREVAABABIAAAAKVcQAAAAAAAEAAAAAAAAAqHV1aWRVU01UIdJPzruIaVz6ycdAAAAAkE1URFQABAAMAAAAC1XEAAACHAAeAAAABBXHAAEAQQBWAFMAIABNAGUAZABpAGEAAAAqAAAAASoOAAEAZABlAHQAZQBjAHQAXwBhAHUAdABvAHAAbABhAHkAAAAyAAAAA1XEAAEAMgAwADAANQBtAGUALwAwADcALwAwADYAMAA2ACAAMwA6ADUAOgAwAAABA21kYXQAAAAYZ01AM5p0KT8uAiAAAAMAIAAAAwDR4wZUAAAABGjuPIAAAAAnZYiAIAAR//eBLT+oL1eA2Nlb/edvwWZflzEVLlhlXtJvSAEGRA3ZAAAACkGaAQCyJ/8AFBAAAAAJQZoCATP/AOmBAAAACUGaAwGz/wDpgAAAAAlBmgQCM/8A6YEAAAAJQZoFArP/AOmBAAAACUGaBgMz/wDpgQAAAAlBmgcDs/8A6YEAAAAJQZoIBDP/AOmAAAAACUGaCQSz/wDpgAAAAAlBmgoFM/8A6YEAAAAJQZoLBbP/AOmAAAAACkGaDAYyJ/8AFBAAAAAKQZoNBrIv/4cMeQ==";
446 | videoElement.appendChild(mp4Source);
447 |
448 | var webmSource = document.createElement('source');
449 | webmSource.src = "data:video/webm;base64,GkXfo49CgoR3ZWJtQoeBAUKFgQEYU4BnAQAAAAAAF60RTZt0vE27jFOrhBVJqWZTrIIQA027jFOrhBZUrmtTrIIQbE27jFOrhBFNm3RTrIIXmU27jFOrhBxTu2tTrIIWs+xPvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUmpZuQq17GDD0JATYCjbGliZWJtbCB2MC43LjcgKyBsaWJtYXRyb3NrYSB2MC44LjFXQY9BVlNNYXRyb3NrYUZpbGVEiYRFnEAARGGIBc2Lz1QNtgBzpJCy3XZ0KNuKNZS4+fDpFxzUFlSua9iu1teBAXPFhL4G+bmDgQG5gQGIgQFVqoEAnIEAbeeBASMxT4Q/gAAAVe6BAIaFVl9WUDiqgQEj44OEE95DVSK1nIN1bmTgkbCBULqBPJqBAFSwgVBUuoE87EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9DtnVB4eeBAKC4obaBAAAAkAMAnQEqUAA8AABHCIWFiIWEiAICAAamYnoOC6cfJa8f5Zvda4D+/7YOf//nNefQYACgnKGWgQFNANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQKbANEBAAEQEAAYABhYL/QACIhgAPuC/rKgnKGWgQPoANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQU1ANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQaDANEBAAEQEAAYABhYL/QACIhgAPuC/rKgnKGWgQfQANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQkdANEBAAEQEBRgAGFgv9AAIiGAAPuC/rOgnKGWgQprANEBAAEQEAAYABhYL/QACIhgAPuC/rKgnKGWgQu4ANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQ0FANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQ5TANEBAAEQEAAYABhYL/QACIhgAPuC/rKgnKGWgQ+gANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgRDtANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgRI7ANEBAAEQEAAYABhYL/QACIhgAPuC/rIcU7trQOC7jLOBALeH94EB8YIUzLuNs4IBTbeH94EB8YIUzLuNs4ICm7eH94EB8YIUzLuNs4ID6LeH94EB8YIUzLuNs4IFNbeH94EB8YIUzLuNs4IGg7eH94EB8YIUzLuNs4IH0LeH94EB8YIUzLuNs4IJHbeH94EB8YIUzLuNs4IKa7eH94EB8YIUzLuNs4ILuLeH94EB8YIUzLuNs4INBbeH94EB8YIUzLuNs4IOU7eH94EB8YIUzLuNs4IPoLeH94EB8YIUzLuNs4IQ7beH94EB8YIUzLuNs4ISO7eH94EB8YIUzBFNm3SPTbuMU6uEH0O2dVOsghTM";
450 | videoElement.appendChild(webmSource);
451 |
452 | document.body.appendChild(videoElement);
453 |
454 | this.async(function() {
455 | // Ideally, we'll get a 'playing' event if we're on a browser that supports
456 | // programmatic play().
457 | videoElement.onplaying = function(e) {
458 | clearTimeout(timeout);
459 |
460 | this.playsupported = (e && e.type === 'playing') || videoElement.currentTime !== 0;
461 | this._playsupportedLocalStorage = this.playsupported;
462 |
463 | videoElement.onplaying = null;
464 |
465 | document.body.removeChild(videoElement);
466 | }.bind(this);
467 |
468 | // If we haven't received a 'playing' event within 500ms, then we're most likely on a browser that doesn't
469 | // support programmatic plays. Do a final check after 500ms and set this.playsupported at that point.
470 | timeout = setTimeout(videoElement.onplaying, 500);
471 |
472 | // Try to initiate playback...
473 | videoElement.play();
474 | });
475 | } else {
476 | // If there's no play() method then we know that it's not supported.
477 | this.playsupported = false;
478 | this._playsupportedLocalStorage = false;
479 | }
480 | }
481 | },
482 |
483 | /**
484 | * Sets fluid width/height.
485 | *
486 | * If the fluid attribute is set, the aspect ratio of the video will
487 | * be inferred (if set in pixels), or assumed to be 16:9. The element
488 | * will give itself enough top padding to force the player to use the
489 | * correct aspect ratio, even as the screen size changes.
490 | *
491 | */
492 | ready: function() {
493 | if (this.hasAttribute('fluid')) {
494 | var ratio = parseInt(this.height, 10) / parseInt(this.width, 10);
495 | if (isNaN(ratio)) {
496 | ratio = 9/16;
497 | }
498 | ratio *= 100;
499 | this.width = '100%';
500 | this.height = 'auto';
501 | this.$.container.style['padding-top'] = ratio + '%';
502 | }
503 | },
504 |
505 | /**
506 | * Clean up the underlying Player `