OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // This Polymer element shows information from media that is currently cast | 5 // This Polymer element shows information from media that is currently cast |
6 // to a device. | 6 // to a device. |
7 Polymer({ | 7 Polymer({ |
8 is: 'route-details', | 8 is: 'route-details', |
9 | 9 |
10 properties: { | 10 properties: { |
11 /** | 11 /** |
12 * The text for the current casting activity status. | 12 * The text for the current casting activity status. |
13 * @private {string|undefined} | 13 * @private {string|undefined} |
14 */ | 14 */ |
15 activityStatus_: { | 15 activityStatus_: { |
16 type: String, | 16 type: String, |
17 }, | 17 }, |
18 | 18 |
19 /** | 19 /** |
20 * Whether the external container will accept change-route-source-click | 20 * Whether the external container will accept change-route-source-click |
21 * events. | 21 * events. |
22 * @private {boolean} | 22 * @private {boolean} |
23 */ | 23 */ |
24 changeRouteSourceAvailable_: { | 24 changeRouteSourceAvailable_: { |
25 type: Boolean, | 25 type: Boolean, |
26 computed: 'computeChangeRouteSourceAvailable_(route, sink,' + | 26 computed: 'computeChangeRouteSourceAvailable_(route, sink,' + |
27 'isAnySinkCurrentlyLaunching, shownCastModeValue)', | 27 'isAnySinkCurrentlyLaunching, shownCastModeValue)', |
28 }, | 28 }, |
29 | 29 |
30 /** | 30 /** |
| 31 * The current time displayed in milliseconds. |
| 32 * @type {number} |
| 33 */ |
| 34 displayedCurrentTime_: { |
| 35 type: Number, |
| 36 value: 0 |
| 37 }, |
| 38 |
| 39 /** |
| 40 * Whether updates for the current time from the browser should be ignored. |
| 41 * Set to true when the user is dragging the time slider. |
| 42 * @private {boolean} |
| 43 */ |
| 44 ignoreExternalTimeUpdates_: { |
| 45 type: Boolean, |
| 46 value: false |
| 47 }, |
| 48 |
| 49 /** |
| 50 * Whether volume updates from the browser should be ignored. Set to true |
| 51 * when the user is dragging the volume slider. |
| 52 * @private {boolean} |
| 53 */ |
| 54 ignoreExternalVolumeUpdates_: { |
| 55 type: Boolean, |
| 56 value: false |
| 57 }, |
| 58 |
| 59 /** |
31 * Whether a sink is currently launching in the container. | 60 * Whether a sink is currently launching in the container. |
32 * @type {boolean} | 61 * @type {boolean} |
33 */ | 62 */ |
34 isAnySinkCurrentlyLaunching: { | 63 isAnySinkCurrentlyLaunching: { |
35 type: Boolean, | 64 type: Boolean, |
36 value: false, | 65 value: false, |
37 }, | 66 }, |
38 | 67 |
39 /** | 68 /** |
40 * The route to show. | 69 * The route to show. |
41 * @type {?media_router.Route|undefined} | 70 * @type {?media_router.Route|undefined} |
42 */ | 71 */ |
43 route: { | 72 route: { |
44 type: Object, | 73 type: Object, |
45 observer: 'maybeLoadCustomController_', | 74 }, |
| 75 |
| 76 |
| 77 /** |
| 78 * The status of the media route shown. |
| 79 * @type {?media_router.RouteStatus} |
| 80 */ |
| 81 routeStatus: { |
| 82 type: Object, |
| 83 observer: 'onRouteStatusChange_', |
| 84 value: null |
46 }, | 85 }, |
47 | 86 |
48 /** | 87 /** |
49 * The cast mode shown to the user. Initially set to auto mode. (See | 88 * The cast mode shown to the user. Initially set to auto mode. (See |
50 * media_router.CastMode documentation for details on auto mode.) | 89 * media_router.CastMode documentation for details on auto mode.) |
51 * @type {number} | 90 * @type {number} |
52 */ | 91 */ |
53 shownCastModeValue: { | 92 shownCastModeValue: { |
54 type: Number, | 93 type: Number, |
55 value: media_router.AUTO_CAST_MODE.type, | 94 value: media_router.AUTO_CAST_MODE.type, |
56 }, | 95 }, |
57 | 96 |
58 /** | 97 /** |
59 * Sink associated with |route|. | 98 * Sink associated with |route|. |
60 * @type {?media_router.Sink} | 99 * @type {?media_router.Sink} |
61 */ | 100 */ |
62 sink: { | 101 sink: { |
63 type: Object, | 102 type: Object, |
64 value: null, | 103 value: null, |
65 }, | 104 }, |
66 | 105 |
67 /** | 106 /** |
68 * Whether the custom controller should be hidden. | 107 * The value of the time slider, between 0 and 100. |
69 * A custom controller is shown iff |route| specifies customControllerPath | 108 * @type {number} |
70 * and the view can be loaded. | |
71 * @private {boolean} | |
72 */ | 109 */ |
73 isCustomControllerHidden_: { | 110 timeSliderValue_: { |
74 type: Boolean, | 111 type: Number, |
75 value: true, | 112 value: 0 |
| 113 }, |
| 114 |
| 115 /** |
| 116 * The value of the volume slider, between 0 and 100. |
| 117 * @type {number} |
| 118 */ |
| 119 volumeSliderValue_: { |
| 120 type: Number, |
| 121 value: 0 |
76 }, | 122 }, |
77 }, | 123 }, |
78 | 124 |
79 behaviors: [ | 125 behaviors: [ |
80 I18nBehavior, | 126 I18nBehavior, |
81 ], | 127 ], |
82 | 128 |
83 /** | 129 /** |
84 * Fires a close-route event. This is called when the button to close | 130 * Fires a close-route event. This is called when the button to close |
85 * the current route is clicked. | 131 * the current route is clicked. |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
148 if (!sink) { | 194 if (!sink) { |
149 return 0; | 195 return 0; |
150 } | 196 } |
151 if (castMode == media_router.CastModeType.AUTO) { | 197 if (castMode == media_router.CastModeType.AUTO) { |
152 return sink.castModes & -sink.castModes; | 198 return sink.castModes & -sink.castModes; |
153 } | 199 } |
154 return castMode & sink.castModes; | 200 return castMode & sink.castModes; |
155 }, | 201 }, |
156 | 202 |
157 /** | 203 /** |
| 204 * @param {number} seekPosition The seek position in milliseconds. |
| 205 * @param {number} duration The duration in milliseconds. |
| 206 * @return {number} The seek position as a percentage between 0 and 100. |
| 207 * |
| 208 * @private |
| 209 */ |
| 210 getSeekPositionPercentage_: function(seekPosition, duration) { |
| 211 return duration ? (seekPosition / duration * 100) : 0; |
| 212 }, |
| 213 |
| 214 /** |
| 215 * @param {number} seekPositionPercentage Must be between 0 and 100. |
| 216 * @param {number} duration The duration in milliseconds. |
| 217 * @return {number} The seek position in milliseconds. |
| 218 * |
| 219 * @private |
| 220 */ |
| 221 getSeekPosition_: function(seekPositionPercentage, duration) { |
| 222 return duration ? (seekPositionPercentage * duration / 100) : 0; |
| 223 }, |
| 224 |
| 225 /** |
| 226 * Gets the duration formatted in HH:MM:SS format. |
| 227 * @param {?media_router.RouteStatus} routeStatus |
| 228 * @return {string} |
| 229 * |
| 230 * @private |
| 231 */ |
| 232 getDuration_: function(routeStatus) { |
| 233 return routeStatus ? this.getFormattedTime_(routeStatus.duration) : ''; |
| 234 }, |
| 235 |
| 236 /** |
| 237 * Converts a number representing an interval of milliseconds to a string with |
| 238 * HH:MM:SS format. |
| 239 * @param {number} timeInMilliSec Must be positive. Intervals longer than 100 |
| 240 * hours get truncated silently. |
| 241 * @return {string} |
| 242 * |
| 243 * @private |
| 244 */ |
| 245 getFormattedTime_: function(timeInMilliSec) { |
| 246 if (timeInMilliSec < 0) { |
| 247 return ''; |
| 248 } |
| 249 var hours = Math.floor(timeInMilliSec / 3600 / 1000); |
| 250 var minutes = Math.floor(timeInMilliSec / 60 / 1000) % 60; |
| 251 var seconds = Math.floor(timeInMilliSec / 1000) % 60; |
| 252 var timeParts = [ |
| 253 ('0' + hours).substr(-2), ('0' + minutes).substr(-2), |
| 254 ('0' + seconds).substr(-2) |
| 255 ]; |
| 256 return timeParts.join(':'); |
| 257 }, |
| 258 |
| 259 /** |
| 260 * @param {?media_router.RouteStatus} routeStatus |
| 261 * @return {string} The value for the icon attribute of the mute/unmute |
| 262 * button. |
| 263 * |
| 264 * @private |
| 265 */ |
| 266 getMuteUnmuteIcon_: function(routeStatus) { |
| 267 return (routeStatus && routeStatus.isMuted) ? 'av:volume-off' : |
| 268 'av:volume-up'; |
| 269 }, |
| 270 |
| 271 /** |
| 272 * @param {?media_router.RouteStatus} routeStatus |
| 273 * @return {string} Localized title for the mute/unmute button. |
| 274 * |
| 275 * @private |
| 276 */ |
| 277 getMuteUnmuteTitle_: function(routeStatus) { |
| 278 return (routeStatus && routeStatus.isMuted) ? this.i18n('unmuteTitle') : |
| 279 this.i18n('muteTitle'); |
| 280 }, |
| 281 |
| 282 /** |
| 283 * @param {?media_router.RouteStatus} routeStatus |
| 284 * @return {string}The value for the icon attribute of the play/pause button. |
| 285 * |
| 286 * @private |
| 287 */ |
| 288 getPlayPauseIcon_: function(routeStatus) { |
| 289 return (routeStatus && routeStatus.isPaused) ? 'av:play-arrow' : 'av:pause'; |
| 290 }, |
| 291 |
| 292 /** |
| 293 * @param {?media_router.RouteStatus} routeStatus |
| 294 * @return {string} Localized title for the play/pause button. |
| 295 * |
| 296 * @private |
| 297 */ |
| 298 getPlayPauseTitle_: function(routeStatus) { |
| 299 return (routeStatus && routeStatus.isPaused) ? this.i18n('playTitle') : |
| 300 this.i18n('pauseTitle'); |
| 301 }, |
| 302 |
| 303 /** |
| 304 * Sends a play or pause command to the browser. |
| 305 * |
| 306 * @private |
| 307 */ |
| 308 onPlayPause_: function() { |
| 309 if (this.routeStatus.isPaused) { |
| 310 this.fire('play-route'); |
| 311 } else { |
| 312 this.fire('pause-route'); |
| 313 } |
| 314 }, |
| 315 |
| 316 /** |
| 317 * Sends a mute or unmute command to the browser. |
| 318 * |
| 319 * @private |
| 320 */ |
| 321 onMuteUnmute_: function() { |
| 322 this.fire('set-route-mute', {mute: !this.routeStatus.isMuted}); |
| 323 }, |
| 324 |
| 325 /** |
| 326 * Updates seek and volume sliders if the user is not currently dragging on |
| 327 * them. |
| 328 * @param {?media_router.RouteStatus} newRouteStatus |
| 329 * @param {?media_router.RouteStatus} oldRouteStatus |
| 330 * |
| 331 * @private |
| 332 */ |
| 333 onRouteStatusChange_: function(newRouteStatus, oldRouteStatus) { |
| 334 if (!newRouteStatus) |
| 335 return; |
| 336 |
| 337 if (!this.ignoreExternalTimeUpdates_) { |
| 338 this.displayedCurrentTime_ = newRouteStatus.currentTime; |
| 339 this.timeSliderValue_ = this.getSeekPositionPercentage_( |
| 340 newRouteStatus.currentTime, newRouteStatus.duration); |
| 341 } |
| 342 |
| 343 if (!this.ignoreExternalVolumeUpdates_) { |
| 344 this.volumeSliderValue_ = newRouteStatus.volume; |
| 345 } |
| 346 }, |
| 347 |
| 348 /** |
| 349 * Called when the user starts dragging the current-time slider. |
| 350 * @param {!Event} e |
| 351 * |
| 352 * @private |
| 353 */ |
| 354 onImmediateTimeSliderChange_: function(e) { |
| 355 this.ignoreExternalTimeUpdates_ = true; |
| 356 /** @type {{immediateValue: number}} */ |
| 357 var target = e.target; |
| 358 this.timeSliderValue_ = target.immediateValue; |
| 359 this.displayedCurrentTime_ = this.getSeekPosition_( |
| 360 this.timeSliderValue_, this.routeStatus.duration); |
| 361 }, |
| 362 |
| 363 /** |
| 364 * Called when the user clicks on or stops dragging the current-time slider. |
| 365 * @param {!Event} e |
| 366 * |
| 367 * @private |
| 368 */ |
| 369 onTimeSliderChange_: function(e) { |
| 370 this.ignoreExternalTimeUpdates_ = false; |
| 371 /** @type {{value: number}} */ |
| 372 var target = e.target; |
| 373 this.timeSliderValue_ = target.value; |
| 374 this.displayedCurrentTime_ = this.getSeekPosition_( |
| 375 this.timeSliderValue_, this.routeStatus.duration); |
| 376 this.fire('seek-route', {time: this.displayedCurrentTime_}); |
| 377 }, |
| 378 |
| 379 /** |
| 380 * Called when the user starts dragging the volume slider. |
| 381 * @param {!Event} e |
| 382 * |
| 383 * @private |
| 384 */ |
| 385 onImmediateVolumeSliderChange_: function(e) { |
| 386 this.ignoreExternalVolumeUpdates_ = true; |
| 387 /** @type {{immediateValue: number}} */ |
| 388 var target = e.target; |
| 389 this.volumeSliderValue_ = target.immediateValue; |
| 390 }, |
| 391 |
| 392 /** |
| 393 * Called when the user clicks on or stops dragging the volume slider. |
| 394 * @param {!Event} e |
| 395 * |
| 396 * @private |
| 397 */ |
| 398 onVolumeSliderChange_: function(e) { |
| 399 this.ignoreExternalVolumeUpdates_ = false; |
| 400 /** @type {{value: number}} */ |
| 401 var target = e.target; |
| 402 this.volumeSliderValue_ = target.value; |
| 403 this.fire('set-route-volume', {volume: this.volumeSliderValue_}); |
| 404 }, |
| 405 |
| 406 /** |
158 * Fires a join-route-click event if the current route is joinable, otherwise | 407 * Fires a join-route-click event if the current route is joinable, otherwise |
159 * it fires a change-route-source-click event, which changes the source of the | 408 * it fires a change-route-source-click event, which changes the source of the |
160 * current route. This may cause the current route to be closed and a new | 409 * current route. This may cause the current route to be closed and a new |
161 * route to be started. This is called when the button to start casting to the | 410 * route to be started. This is called when the button to start casting to the |
162 * current route is clicked. | 411 * current route is clicked. |
163 * | 412 * |
164 * @private | 413 * @private |
165 */ | 414 */ |
166 startCastingToRoute_: function() { | 415 startCastingToRoute_: function() { |
167 if (this.route.canJoin) { | 416 if (this.route.canJoin) { |
168 this.fire('join-route-click', {route: this.route}); | 417 this.fire('join-route-click', {route: this.route}); |
169 } else { | 418 } else { |
170 this.fire('change-route-source-click', { | 419 this.fire('change-route-source-click', { |
171 route: this.route, | 420 route: this.route, |
172 selectedCastMode: | 421 selectedCastMode: |
173 this.computeSelectedCastMode_(this.shownCastModeValue, this.sink) | 422 this.computeSelectedCastMode_(this.shownCastModeValue, this.sink) |
174 }); | 423 }); |
175 } | 424 } |
176 }, | 425 }, |
177 | |
178 /** | |
179 * Loads the custom controller if |route.customControllerPath| exists. | |
180 * Falls back to the default route details view otherwise, or if load fails. | |
181 * Updates |activityStatus_| for the default view. | |
182 * | |
183 * @private | |
184 */ | |
185 maybeLoadCustomController_: function() { | |
186 this.activityStatus_ = this.route ? | |
187 loadTimeData.getStringF('castingActivityStatus', | |
188 this.route.description) : | |
189 ''; | |
190 | |
191 if (!this.route || !this.route.customControllerPath) { | |
192 this.isCustomControllerHidden_ = true; | |
193 return; | |
194 } | |
195 | |
196 // Show custom controller | |
197 var extensionview = this.$['custom-controller']; | |
198 | |
199 // Do nothing if the url is the same and the view is not hidden. | |
200 if (this.route.customControllerPath == extensionview.src && | |
201 !this.isCustomControllerHidden_) | |
202 return; | |
203 | |
204 var that = this; | |
205 extensionview.load(this.route.customControllerPath) | |
206 .then(function() { | |
207 // Load was successful; show the custom controller. | |
208 that.isCustomControllerHidden_ = false; | |
209 }, function() { | |
210 // Load was unsuccessful; fall back to default view. | |
211 that.isCustomControllerHidden_ = true; | |
212 }); | |
213 }, | |
214 }); | 426 }); |
OLD | NEW |