OLD | NEW |
(Empty) | |
| 1 <!-- |
| 2 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| 3 This code may only be used under the BSD style license found at https://polymer.
github.io/LICENSE.txt |
| 4 The complete set of authors may be found at https://polymer.github.io/AUTHORS.tx
t |
| 5 The complete set of contributors may be found at https://polymer.github.io/CONTR
IBUTORS.txt |
| 6 Code distributed by Google as part of the polymer project is also |
| 7 subject to an additional IP rights grant found at https://polymer.github.io/PATE
NTS.txt |
| 8 --> |
| 9 |
| 10 <link rel="import" href="../polymer/polymer.html"> |
| 11 <link rel="import" href="../iron-localstorage/iron-localstorage.html"> |
| 12 <link rel="import" href="../google-apis/google-youtube-api.html"> |
| 13 |
| 14 <!-- |
| 15 `google-youtube` encapsulates the YouTube player into a web component. |
| 16 |
| 17 <google-youtube |
| 18 video-id="..." |
| 19 height="270px" |
| 20 width="480px" |
| 21 rel="0" |
| 22 start="5" |
| 23 autoplay="1"> |
| 24 </google-youtube> |
| 25 |
| 26 `google-youtube` supports all of the [embedded player parameters](https://develo
pers.google.com/youtube/player_parameters). Each can be set as an attribute on `
google-youtube`. |
| 27 |
| 28 The standard set of [YouTube player events](https://developers.google.com/youtub
e/iframe_api_reference#Events) are exposed, as well as methods for playing, paus
ing, seeking to a specific time, and loading a new video. |
| 29 |
| 30 @demo |
| 31 --> |
| 32 <dom-module id="google-youtube"> |
| 33 <template> |
| 34 <style> |
| 35 :host { |
| 36 display: block; |
| 37 } |
| 38 |
| 39 :host([fluid]) { |
| 40 width: 100%; |
| 41 max-width: 100%; |
| 42 position: relative; |
| 43 } |
| 44 |
| 45 :host([fluid]) iframe, |
| 46 :host([fluid]) #thumbnail { |
| 47 vertical-align: bottom; |
| 48 position: absolute; |
| 49 top: 0px; |
| 50 left: 0px; |
| 51 width: 100%; |
| 52 height: 100%; |
| 53 } |
| 54 |
| 55 #container { |
| 56 max-width: 100%; |
| 57 max-height: 100%; |
| 58 } |
| 59 |
| 60 #thumbnail { |
| 61 width: 100%; |
| 62 height: 100%; |
| 63 cursor: pointer; |
| 64 } |
| 65 |
| 66 /* Some browsers will refuse to play videos with 'display: none' set, so p
osition the video well offscreen instead. */ |
| 67 #playtest { |
| 68 position: absolute; |
| 69 top: -9999px; |
| 70 left: -9999px; |
| 71 } |
| 72 </style> |
| 73 |
| 74 <div id="container" style$="{{_computeContainerStyle(width, height)}}"> |
| 75 <template is="dom-if" if="{{thumbnail}}"> |
| 76 <img id="thumbnail" |
| 77 src$="{{thumbnail}}" |
| 78 title="YouTube video thumbnail." |
| 79 alt="YouTube video thumbnail." |
| 80 on-tap="_handleThumbnailTap"> |
| 81 </template> |
| 82 |
| 83 <template is="dom-if" if="{{!thumbnail}}"> |
| 84 <google-youtube-api on-api-load="_apiLoad"></google-youtube-api> |
| 85 </template> |
| 86 |
| 87 <!-- Use this._playsupportedLocalStorage as the value, since this.playsupp
orted is set to |
| 88 true as soon as initial playback has started, and we don't want that
cached. --> |
| 89 <iron-localstorage name="google-youtube-playsupported" |
| 90 value="_playsupportedLocalStorage" |
| 91 on-iron-localstorage-load="_determinePlaySupported"> |
| 92 </iron-localstorage> |
| 93 |
| 94 <div id="player"></div> |
| 95 </div> |
| 96 </template> |
| 97 </dom-module> |
| 98 |
| 99 <script> |
| 100 Polymer({ |
| 101 is: 'google-youtube', |
| 102 /** |
| 103 * Fired when the YouTube player is fully initialized and ready for use. |
| 104 * |
| 105 * @event google-youtube-ready |
| 106 */ |
| 107 |
| 108 /** |
| 109 * Fired when the state of the player changes. `e.detail.data` is set to one
of |
| 110 * [the documented](https://developers.google.com/youtube/iframe_api_referen
ce#onStateChange) |
| 111 * states. |
| 112 * |
| 113 * @event google-youtube-state-change |
| 114 * @param {Object} e Event parameters. |
| 115 */ |
| 116 |
| 117 /** |
| 118 * Fired when playback fails due to an error. `e.detail.data` is set to one
of |
| 119 * [the documented](https://developers.google.com/youtube/iframe_api_referen
ce#onError) |
| 120 * error codes. |
| 121 * |
| 122 * @event google-youtube-error |
| 123 * @param {Object} e Event parameters. |
| 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 * You can [search for videos programmatically](https://developers.google.co
m/youtube/v3/docs/search/list) |
| 133 * using the YouTube Data API, or just hardcode known video ids to display o
n your page. |
| 134 */ |
| 135 videoId: { |
| 136 type: String, |
| 137 value: 'mN7IAaRdi_k', |
| 138 observer: '_videoIdChanged' |
| 139 }, |
| 140 |
| 141 /** |
| 142 * Whether programmatic `<video>.play()` for initial playback is supported
in the current browser. |
| 143 * |
| 144 * Most mobile browsers [do not support](https://developer.apple.com/libra
ry/safari/documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/Device-Spe
cificConsiderations/Device-SpecificConsiderations.html#//apple_ref/doc/uid/TP400
09523-CH5-SW1) autoplaying or scripted playback of videos. |
| 145 * If you attempt to automatically initiate playback of a `<google-youtube
>`, e.g. by calling the `play()` method before |
| 146 * playback has initially begun, the YouTube Player will enter an unrecove
rable "stuck" state. |
| 147 * To protect against this, check the value of `playsupported` and don't c
all `play()` if it is set to `false`. |
| 148 * (You can hide/disable your custom play button, etc.) |
| 149 * |
| 150 * The `playsupported` value is determined at runtime, by dynamically crea
ting a `<video>` element with an |
| 151 * inlined data source and calling `play()` on it. (Inspired by [Modernizr
](https://github.com/Modernizr/Modernizr/blob/master/feature-detects/video/autop
lay.js).) |
| 152 * |
| 153 * If you would rather not incur the minimal overhead involved in going th
rough this process, you can explicitly set |
| 154 * `playsupported` to `true` or `false` when initializing `<google-youtube
>`. This is only recommended if you know that |
| 155 * your web app will never (or only) be used on mobile browsers. |
| 156 */ |
| 157 playsupported: { |
| 158 type: Boolean, |
| 159 value: null, |
| 160 notify: true |
| 161 }, |
| 162 |
| 163 /** |
| 164 * "1" if video should start automatically |
| 165 */ |
| 166 autoplay: { |
| 167 type: Number, |
| 168 value: 0 |
| 169 }, |
| 170 /** |
| 171 * Whether playback has started. |
| 172 * |
| 173 * This defaults to `false` and is set to `true` once the first 'playing'
event is fired by |
| 174 * the underlying YouTube Player API. |
| 175 * |
| 176 * Once set to `true`, it will remain that way indefinitely. |
| 177 * Paused/buffering/ended events won't cause `playbackstarted` to reset to
`false`. |
| 178 * Nor will loading a new video into the player. |
| 179 */ |
| 180 playbackstarted: { |
| 181 type: Boolean, |
| 182 value: false, |
| 183 notify: true |
| 184 }, |
| 185 |
| 186 /** |
| 187 * Sets the height of the player on the page. |
| 188 * Accepts anything valid for a CSS measurement, e.g. '200px' or '50%'. |
| 189 * If the unit of measurement is left off, 'px' is assumed. |
| 190 */ |
| 191 height: { |
| 192 type: String, |
| 193 value: '270px' |
| 194 }, |
| 195 |
| 196 /** |
| 197 * Sets the width of the player on the page. |
| 198 * Accepts anything valid for a CSS measurement, e.g. '200px' or '50%'. |
| 199 * If the unit of measurement is left off, 'px' is assumed. |
| 200 */ |
| 201 width: { |
| 202 type: String, |
| 203 value:'480px' |
| 204 }, |
| 205 |
| 206 /** |
| 207 * Exposes the current player state. |
| 208 * Using this attribute is an alternative to listening to `google-youtube-
state-change` events, |
| 209 * and can simplify the logic in templates with conditional binding. |
| 210 * |
| 211 * The [possible values](https://developers.google.com/youtube/iframe_api_
reference#onStateChange): |
| 212 * - -1 (unstarted) |
| 213 * - 0 (ended) |
| 214 * - 1 (playing) |
| 215 * - 2 (paused) |
| 216 * - 3 (buffering) |
| 217 * - 5 (video cued) |
| 218 */ |
| 219 state: { |
| 220 type: Number, |
| 221 value: -1, |
| 222 notify: true |
| 223 }, |
| 224 |
| 225 /** |
| 226 * Exposes the current playback time, in seconds. |
| 227 * |
| 228 * You can divide this value by the `duration` to determine the playback p
ercentage. |
| 229 */ |
| 230 currenttime: { |
| 231 type: Number, |
| 232 value: 0, |
| 233 notify: true |
| 234 }, |
| 235 |
| 236 /** |
| 237 * Exposes the video duration, in seconds. |
| 238 * |
| 239 * You can divide the `currenttime` to determine the playback percentage. |
| 240 * |
| 241 * @attribute duration |
| 242 * @type number |
| 243 */ |
| 244 duration: { |
| 245 type: Number, |
| 246 value: 1, // To avoid divide-by-zero errors if used before video is cued
. |
| 247 notify: true |
| 248 }, |
| 249 |
| 250 /** |
| 251 * Exposes the current playback time, formatted as a (HH:)MM:SS string. |
| 252 */ |
| 253 currenttimeformatted: { |
| 254 type: String, |
| 255 value: '0:00', |
| 256 notify: true |
| 257 }, |
| 258 |
| 259 /** |
| 260 * Exposes the video duration, formatted as a (HH:)MM:SS string. |
| 261 */ |
| 262 durationformatted: { |
| 263 type: String, |
| 264 value: '0:00', // To avoid divide-by-zero errors if used before video is
cued. |
| 265 notify: true |
| 266 }, |
| 267 |
| 268 /** |
| 269 * The fraction of the bytes that have been loaded for the current video,
in the range [0-1]. |
| 270 */ |
| 271 fractionloaded: { |
| 272 type: Number, |
| 273 value: 0, |
| 274 notify: true |
| 275 }, |
| 276 |
| 277 /** |
| 278 * A shorthand to enable a set of player attributes that, used together, s
imulate a "chromeless" YouTube player. |
| 279 * |
| 280 * Equivalent to setting the following attributes: |
| 281 * - `controls="0"` |
| 282 * - `modestbranding="1"` |
| 283 * - `showinfo="0"` |
| 284 * - `iv_load_policy="3"` |
| 285 * - `rel="0"` |
| 286 * |
| 287 * The "chromeless" player has minimal YouTube branding in cued state, and
the native controls |
| 288 * will be disabled during playback. Creating your own custom play/pause/e
tc. controls is recommended. |
| 289 */ |
| 290 chromeless: { |
| 291 type: Boolean, |
| 292 value: false |
| 293 }, |
| 294 /** |
| 295 * The URL of an image to use as a custom thumbnail. |
| 296 * |
| 297 * This is optional; if not provided, the standard YouTube embed (which us
es the thumbnail associated |
| 298 * with the video on YouTube) will be used. |
| 299 * |
| 300 * If `thumbnail` is set, than an `<img>` containing the thumbnail will be
used in lieu of the actual |
| 301 * YouTube embed. When the thumbnail is clicked, the `<img>` is swapped ou
t for the actual YouTube embed, |
| 302 * which will have [`autoplay=1`](https://developers.google.com/youtube/pl
ayer_parameters#autoplay) set by default (in additional to any other player para
meters specified on this element). |
| 303 * |
| 304 * Please note that `autoplay=1` won't actually autoplay videos on mobile
browsers, so two taps will be required |
| 305 * to play the video there. Also, on desktop browsers, setting `autoplay=1
` will prevent the playback |
| 306 * from [incrementing the view count](https://support.google.com/youtube/a
nswer/1714329) for the video. |
| 307 */ |
| 308 thumbnail: { |
| 309 type: String, |
| 310 value: '' |
| 311 }, |
| 312 |
| 313 /** |
| 314 * If `fluid` is set, then the player will set its width to 100% to fill |
| 315 * the parent container, while adding `padding-top` to preserve the |
| 316 * aspect ratio provided by `width` and `height`. If `width` and `height` |
| 317 * have not been set, the player will fall back to a 16:9 aspect ratio. |
| 318 * This is useful for responsive designs where you don't want to |
| 319 * introduce letterboxing on your video. |
| 320 */ |
| 321 fluid: { |
| 322 type: Boolean, |
| 323 value: false |
| 324 } |
| 325 |
| 326 }, |
| 327 |
| 328 _computeContainerStyle: function(width, height) { |
| 329 return 'width:' + width + '; height:' + height; |
| 330 }, |
| 331 /** |
| 332 * Detects whether programmatic <video>.play() is supported in the current b
rowser. |
| 333 * |
| 334 * This is triggered via on-ironlocalstorage-load. The logic is: |
| 335 * - If playsupported is explicitly set to true or false on the element, use
that. |
| 336 * - Otherwise, if there's a cached value in localStorage, use that. |
| 337 * - Otherwise, create a hidden <video> element and call play() on it: |
| 338 * - If playback starts, playsupported is true. |
| 339 * - If playback doesn't start (within 500ms), playsupported is false. |
| 340 * - Whatever happens, cache the result in localStorage. |
| 341 */ |
| 342 _determinePlaySupported: function() { |
| 343 // If playsupported isn't already being overridden by the page using this
component, then attempt |
| 344 // to determine if it's supported. |
| 345 // This is deliberately checking with ==, to match either undefined or nul
l. |
| 346 if (this.playsupported == null) { |
| 347 // If we don't have the results of a previous test cached in localStorag
e, then run a new playback test. |
| 348 if (this._playsupportedLocalStorage == null) { |
| 349 var timeout; |
| 350 var videoElement = document.createElement('video'); |
| 351 |
| 352 if ('play' in videoElement) { |
| 353 videoElement.id = 'playtest'; |
| 354 |
| 355 var mp4Source = document.createElement('source'); |
| 356 mp4Source.src = "data:video/mp4;base64,AAAAFGZ0eXBNU05WAAACAE1TTlYAA
AOUbW9vdgAAAGxtdmhkAAAAAM9ghv7PYIb+AAACWAAACu8AAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAA
AAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAnh0c
mFrAAAAXHRraGQAAAAHz2CG/s9ghv4AAAABAAAAAAAACu8AAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAA
AAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAFAAAAA4AAAAAAHgbWRpYQAAACBtZGhkAAAAAM9ghv7PY
Ib+AAALuAAANq8AAAAAAAAAIWhkbHIAAAAAbWhscnZpZGVBVlMgAAAAAAABAB4AAAABl21pbmYAAAAUd
m1oZAAAAAAAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAVdzdGJsA
AAAp3N0c2QAAAAAAAAAAQAAAJdhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAFAAOABIAAAASAAAA
AAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGP//AAAAEmNvbHJuY2xjAAEAAQABA
AAAL2F2Y0MBTUAz/+EAGGdNQDOadCk/LgIgAAADACAAAAMA0eMGVAEABGjuPIAAAAAYc3R0cwAAAAAAA
AABAAAADgAAA+gAAAAUc3RzcwAAAAAAAAABAAAAAQAAABxzdHNjAAAAAAAAAAEAAAABAAAADgAAAAEAA
ABMc3RzegAAAAAAAAAAAAAADgAAAE8AAAAOAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AA
AANAAAADQAAAA4AAAAOAAAAFHN0Y28AAAAAAAAAAQAAA7AAAAA0dXVpZFVTTVQh0k/Ou4hpXPrJx0AAA
AAcTVREVAABABIAAAAKVcQAAAAAAAEAAAAAAAAAqHV1aWRVU01UIdJPzruIaVz6ycdAAAAAkE1URFQAB
AAMAAAAC1XEAAACHAAeAAAABBXHAAEAQQBWAFMAIABNAGUAZABpAGEAAAAqAAAAASoOAAEAZABlAHQAZ
QBjAHQAXwBhAHUAdABvAHAAbABhAHkAAAAyAAAAA1XEAAEAMgAwADAANQBtAGUALwAwADcALwAwADYAM
AA2ACAAMwA6ADUAOgAwAAABA21kYXQAAAAYZ01AM5p0KT8uAiAAAAMAIAAAAwDR4wZUAAAABGjuPIAAA
AAnZYiAIAAR//eBLT+oL1eA2Nlb/edvwWZflzEVLlhlXtJvSAEGRA3ZAAAACkGaAQCyJ/8AFBAAAAAJQ
ZoCATP/AOmBAAAACUGaAwGz/wDpgAAAAAlBmgQCM/8A6YEAAAAJQZoFArP/AOmBAAAACUGaBgMz/wDpg
QAAAAlBmgcDs/8A6YEAAAAJQZoIBDP/AOmAAAAACUGaCQSz/wDpgAAAAAlBmgoFM/8A6YEAAAAJQZoLB
bP/AOmAAAAACkGaDAYyJ/8AFBAAAAAKQZoNBrIv/4cMeQ=="; |
| 357 videoElement.appendChild(mp4Source); |
| 358 |
| 359 var webmSource = document.createElement('source'); |
| 360 webmSource.src = "data:video/webm;base64,GkXfo49CgoR3ZWJtQoeBAUKFgQE
YU4BnAQAAAAAAF60RTZt0vE27jFOrhBVJqWZTrIIQA027jFOrhBZUrmtTrIIQbE27jFOrhBFNm3RTrII
XmU27jFOrhBxTu2tTrIIWs+xPvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUmpZuQq17GDD0JATYCjbGliZWJtbCB2MC43Ljc
gKyBsaWJtYXRyb3NrYSB2MC44LjFXQY9BVlNNYXRyb3NrYUZpbGVEiYRFnEAARGGIBc2Lz1QNtgBzpJC
y3XZ0KNuKNZS4+fDpFxzUFlSua9iu1teBAXPFhL4G+bmDgQG5gQGIgQFVqoEAnIEAbeeBASMxT4Q/gAA
AVe6BAIaFVl9WUDiqgQEj44OEE95DVSK1nIN1bmTgkbCBULqBPJqBAFSwgVBUuoE87EQAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9DtnV
B4eeBAKC4obaBAAAAkAMAnQEqUAA8AABHCIWFiIWEiAICAAamYnoOC6cfJa8f5Zvda4D+/7YOf//nNef
QYACgnKGWgQFNANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQKbANEBAAEQEAAYABhYL/QACIhgAPu
C/rKgnKGWgQPoANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQU1ANEBAAEQEAAYABhYL/QACIhgAPu
C/rOgnKGWgQaDANEBAAEQEAAYABhYL/QACIhgAPuC/rKgnKGWgQfQANEBAAEQEAAYABhYL/QACIhgAPu
C/rOgnKGWgQkdANEBAAEQEBRgAGFgv9AAIiGAAPuC/rOgnKGWgQprANEBAAEQEAAYABhYL/QACIhgAPu
C/rKgnKGWgQu4ANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgQ0FANEBAAEQEAAYABhYL/QACIhgAPu
C/rOgnKGWgQ5TANEBAAEQEAAYABhYL/QACIhgAPuC/rKgnKGWgQ+gANEBAAEQEAAYABhYL/QACIhgAPu
C/rOgnKGWgRDtANEBAAEQEAAYABhYL/QACIhgAPuC/rOgnKGWgRI7ANEBAAEQEAAYABhYL/QACIhgAPu
C/rIcU7trQOC7jLOBALeH94EB8YIUzLuNs4IBTbeH94EB8YIUzLuNs4ICm7eH94EB8YIUzLuNs4ID6Le
H94EB8YIUzLuNs4IFNbeH94EB8YIUzLuNs4IGg7eH94EB8YIUzLuNs4IH0LeH94EB8YIUzLuNs4IJHbe
H94EB8YIUzLuNs4IKa7eH94EB8YIUzLuNs4ILuLeH94EB8YIUzLuNs4INBbeH94EB8YIUzLuNs4IOU7e
H94EB8YIUzLuNs4IPoLeH94EB8YIUzLuNs4IQ7beH94EB8YIUzLuNs4ISO7eH94EB8YIUzBFNm3SPTbu
MU6uEH0O2dVOsghTM"; |
| 361 videoElement.appendChild(webmSource); |
| 362 |
| 363 document.body.appendChild(videoElement); |
| 364 |
| 365 this.async(function() { |
| 366 // Ideally, we'll get a 'playing' event if we're on a browser that
supports programmatic play(). |
| 367 videoElement.onplaying = function(e) { |
| 368 clearTimeout(timeout); |
| 369 |
| 370 this.playsupported = (e && e.type === 'playing') || videoElement
.currentTime !== 0; |
| 371 this._playsupportedLocalStorage = this.playsupported; |
| 372 |
| 373 videoElement.onplaying = null; |
| 374 |
| 375 document.body.removeChild(videoElement); |
| 376 }.bind(this); |
| 377 |
| 378 // If we haven't received a 'playing' event within 500ms, then we'
re most likely on a browser that doesn't |
| 379 // support programmatic plays. Do a final check after 500ms and se
t this.playsupported at that point. |
| 380 timeout = setTimeout(videoElement.onplaying, 500); |
| 381 |
| 382 // Try to initiate playback... |
| 383 videoElement.play(); |
| 384 }); |
| 385 } else { |
| 386 // If there's no play() method then we know that it's no supported. |
| 387 this.playsupported = false; |
| 388 this._playsupportedLocalStorage = false; |
| 389 } |
| 390 } else { |
| 391 // If we do have the results of a previous test cached, then just use
that. |
| 392 // A browser's support for <video>.play() isn't likely to change. |
| 393 this.playsupported = this._playsupportedLocalStorage; |
| 394 } |
| 395 } |
| 396 }, |
| 397 |
| 398 /** |
| 399 * Sets fluid width/height. |
| 400 * |
| 401 * If the fluid attribute is set, the aspect ratio of the video will |
| 402 * be inferred (if set in pixels), or assumed to be 16:9. The element |
| 403 * will give itself enough top padding to force the player to use the |
| 404 * correct aspect ratio, even as the screen size changes. |
| 405 * |
| 406 */ |
| 407 ready: function() { |
| 408 if (this.hasAttribute('fluid')) { |
| 409 var ratio = parseInt(this.height, 10) / parseInt(this.width, 10); |
| 410 if (isNaN(ratio)) { |
| 411 ratio = 9/16; |
| 412 } |
| 413 ratio *= 100; |
| 414 this.width = '100%'; |
| 415 this.height = 'auto'; |
| 416 this.style['padding-top'] = ratio + '%'; |
| 417 } |
| 418 }, |
| 419 |
| 420 /** |
| 421 * Clean up the underlying Player `<iframe>` when we're removed from the DOM
. |
| 422 */ |
| 423 detached: function() { |
| 424 if (this._player) { |
| 425 this._player.destroy(); |
| 426 } |
| 427 }, |
| 428 |
| 429 /** |
| 430 * Plays the current video. |
| 431 * |
| 432 * Note that on certain mobile browsers, playback |
| 433 * [can't be initiated programmatically](https://developers.google.com/youtu
be/iframe_api_reference#Mobile_considerations). |
| 434 * |
| 435 * If `this.playsupported` is not `true`, calling `play()` will have no effe
ct. |
| 436 * |
| 437 * @method play |
| 438 */ |
| 439 play: function() { |
| 440 if (this._player && this._player.playVideo && this.playsupported) { |
| 441 this._player.playVideo(); |
| 442 } |
| 443 }, |
| 444 |
| 445 /** |
| 446 * Modifies the volume of the current video. |
| 447 * |
| 448 * Developers should take care not to break expected user experience by prog
rammatically |
| 449 * modifying the volume on mobile browsers. |
| 450 * Note that the YouTube player, in addition, does not display volume contro
ls in a |
| 451 * mobile environment. |
| 452 * |
| 453 * @method setVolume |
| 454 * @param {number} volume The new volume, an integer between 0 (muted) and 1
00 (loudest). |
| 455 */ |
| 456 setVolume: function(volume) { |
| 457 if (this._player && this._player.setVolume) { |
| 458 this._player.setVolume(volume); |
| 459 } |
| 460 }, |
| 461 |
| 462 /** |
| 463 * Mutes the current video. |
| 464 * |
| 465 * Developers should take care not to break expected user experience by prog
rammatically |
| 466 * modifying the volume on mobile browsers. |
| 467 * Note that the YouTube player, in addition, does not display volume contro
ls in a |
| 468 * mobile environment. |
| 469 * |
| 470 * @method mute |
| 471 */ |
| 472 mute: function() { |
| 473 if (this._player && this._player.mute) { |
| 474 this._player.mute(); |
| 475 } |
| 476 }, |
| 477 |
| 478 /** |
| 479 * Unmutes the current video. |
| 480 * |
| 481 * Developers should take care not to break expected user experience by prog
rammatically |
| 482 * modifying the volume on mobile browsers. |
| 483 * Note that the YouTube player, in addition, does not display volume contro
ls in a |
| 484 * mobile environment. |
| 485 * |
| 486 * @method unMute |
| 487 */ |
| 488 unMute: function() { |
| 489 if (this._player && this._player.unMute) { |
| 490 this._player.unMute(); |
| 491 } |
| 492 }, |
| 493 |
| 494 /** |
| 495 * Pauses the current video. |
| 496 * |
| 497 * @method pause |
| 498 */ |
| 499 pause: function() { |
| 500 if (this._player && this._player.pauseVideo) { |
| 501 this._player.pauseVideo(); |
| 502 } |
| 503 }, |
| 504 |
| 505 /** |
| 506 * Skips ahead (or back) to the specified number of seconds. |
| 507 * |
| 508 * @method seekTo |
| 509 * @param {number} seconds Number of seconds to seek to. |
| 510 */ |
| 511 seekTo: function(seconds) { |
| 512 if (this._player && this._player.seekTo) { |
| 513 this._player.seekTo(seconds, true); |
| 514 |
| 515 // Explicitly call _updatePlaybackStats() to ensure that the new playbac
k info is |
| 516 // reflected in the bound attributes. |
| 517 // The 100ms delay is somewhat arbitrary, but the YouTube player does ne
ed time to |
| 518 // update its internal state following the call to player.seekTo(). |
| 519 this.async(function() { |
| 520 this._updatePlaybackStats(); |
| 521 }, null, 100); |
| 522 } |
| 523 }, |
| 524 |
| 525 _videoIdChanged: function() { |
| 526 this.currenttime = 0; |
| 527 this.currenttimeformatted = this._toHHMMSS(0); |
| 528 this.fractionloaded = 0; |
| 529 this.duration = 1; |
| 530 this.durationformatted = this._toHHMMSS(0); |
| 531 |
| 532 if (!this._player || !this._player.cueVideoById) { |
| 533 this._pendingVideoId = this.videoId; |
| 534 } else { |
| 535 // Figure out whether we should cue or load (which will autoplay) the ne
xt video. |
| 536 if (this.playsupported && this.attributes['autoplay'] && this.attributes
['autoplay'].value == '1') { |
| 537 this._player.loadVideoById(this.videoId); |
| 538 } else { |
| 539 this._player.cueVideoById(this.videoId); |
| 540 } |
| 541 } |
| 542 }, |
| 543 |
| 544 _player: null, |
| 545 __updatePlaybackStatsInterval: null, |
| 546 _pendingVideoId: '', |
| 547 |
| 548 _apiLoad: function() { |
| 549 // Establish some defaults. Attributes set on the google-youtube element |
| 550 // can override defaults, or specify additional player parameters. See |
| 551 // https://developers.google.com/youtube/player_parameters |
| 552 var playerVars = { |
| 553 playsinline: 1, |
| 554 controls: 2, |
| 555 autohide: 1, |
| 556 // This will (intentionally) be overwritten if this.attributes['autoplay
'] is set. |
| 557 autoplay: this.autoplay |
| 558 }; |
| 559 |
| 560 if (this.chromeless) { |
| 561 playerVars.controls = 0; |
| 562 playerVars.modestbranding = 1; |
| 563 playerVars.showinfo = 0; |
| 564 // Disable annotations. |
| 565 playerVars.iv_load_policy = 3; |
| 566 // Disable related videos on the end screen. |
| 567 playerVars.rel = 0; |
| 568 } |
| 569 |
| 570 for (var i = 0; i < this.attributes.length; i++) { |
| 571 var attribute = this.attributes[i]; |
| 572 playerVars[attribute.nodeName] = attribute.value; |
| 573 } |
| 574 |
| 575 this._player = new YT.Player(this.$.player, { |
| 576 videoId: this.videoId, |
| 577 width: '100%', |
| 578 height: '100%', |
| 579 playerVars: playerVars, |
| 580 events: { |
| 581 onReady: function(e) { |
| 582 if (this._pendingVideoId && this._pendingVideoId != this.videoId) { |
| 583 this._player.cueVideoById(this._pendingVideoId); |
| 584 this._pendingVideoId = ''; |
| 585 } |
| 586 |
| 587 this.fire('google-youtube-ready', e); |
| 588 }.bind(this), |
| 589 onStateChange: function(e) { |
| 590 this.state = e.data; |
| 591 |
| 592 // The YouTube Player API only exposes playback data about a video o
nce |
| 593 // playback has begun. |
| 594 if (this.state == 1) { |
| 595 this.playbackstarted = true; |
| 596 |
| 597 // After playback has begun, play() can always be used to resume p
layback if the video is paused. |
| 598 this.playsupported = true; |
| 599 |
| 600 this.duration = this._player.getDuration(); |
| 601 this.durationformatted = this._toHHMMSS(this.duration); |
| 602 |
| 603 if (!this.__updatePlaybackStatsInterval) { |
| 604 this.__updatePlaybackStatsInterval = setInterval(this._updatePla
ybackStats.bind(this), 1000); |
| 605 } |
| 606 } else { |
| 607 // We only need to update the stats if the video is playing. |
| 608 if (this.__updatePlaybackStatsInterval) { |
| 609 clearInterval(this.__updatePlaybackStatsInterval); |
| 610 this.__updatePlaybackStatsInterval = null; |
| 611 } |
| 612 } |
| 613 |
| 614 this.fire('google-youtube-state-change', e); |
| 615 }.bind(this), |
| 616 onError: function(e) { |
| 617 // Set the player state to 0 ('ended'), since playback will have sto
pped. |
| 618 this.state = 0; |
| 619 |
| 620 this.fire('google-youtube-error', e); |
| 621 }.bind(this) |
| 622 } |
| 623 }); |
| 624 }, |
| 625 |
| 626 _updatePlaybackStats: function() { |
| 627 this.currenttime = Math.round(this._player.getCurrentTime()); |
| 628 this.currenttimeformatted = this._toHHMMSS(this.currenttime); |
| 629 this.fractionloaded = this._player.getVideoLoadedFraction(); |
| 630 }, |
| 631 |
| 632 _toHHMMSS: function(totalSeconds) { |
| 633 var hours = Math.floor(totalSeconds / 3600); |
| 634 totalSeconds -= hours * 3600; |
| 635 var minutes = Math.floor(totalSeconds / 60); |
| 636 var seconds = Math.round(totalSeconds - (minutes * 60)); |
| 637 |
| 638 var hourPortion = ''; |
| 639 if (hours > 0) { |
| 640 hourPortion += hours + ':'; |
| 641 |
| 642 if (minutes < 10) { |
| 643 minutes = '0' + minutes; |
| 644 } |
| 645 } |
| 646 |
| 647 if (seconds < 10) { |
| 648 seconds = '0' + seconds; |
| 649 } |
| 650 |
| 651 return hourPortion + minutes + ':' + seconds; |
| 652 }, |
| 653 |
| 654 _handleThumbnailTap: function() { |
| 655 this.autoplay = 1; |
| 656 this.thumbnail = ''; |
| 657 } |
| 658 }); |
| 659 </script> |
OLD | NEW |