Rename MediaPlaybackAllowsInline
[WebKit-https.git] / Source / WebCore / Modules / mediacontrols / mediaControlsiOS.js
1 function createControls(root, video, host)
2 {
3     return new ControllerIOS(root, video, host);
4 };
5
6 function ControllerIOS(root, video, host)
7 {
8     this.doingSetup = true;
9     this._pageScaleFactor = 1;
10
11     this.timelineContextName = "_webkit-media-controls-timeline-" + host.generateUUID();
12
13     Controller.call(this, root, video, host);
14
15     this.setNeedsTimelineMetricsUpdate();
16
17     this._timelineIsHidden = false;
18     this._currentDisplayWidth = 0;
19     this.scheduleUpdateLayoutForDisplayedWidth();
20
21     host.controlsDependOnPageScaleFactor = true;
22     this.doingSetup = false;
23 };
24
25 /* Enums */
26 ControllerIOS.StartPlaybackControls = 2;
27
28
29 ControllerIOS.prototype = {
30     /* Constants */
31     MinimumTimelineWidth: 200,
32     ButtonWidth: 42,
33
34     addVideoListeners: function() {
35         Controller.prototype.addVideoListeners.call(this);
36
37         this.listenFor(this.video, 'webkitbeginfullscreen', this.handleFullscreenChange);
38         this.listenFor(this.video, 'webkitendfullscreen', this.handleFullscreenChange);
39         this.listenFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange);
40     },
41
42     removeVideoListeners: function() {
43         Controller.prototype.removeVideoListeners.call(this);
44
45         this.stopListeningFor(this.video, 'webkitbeginfullscreen', this.handleFullscreenChange);
46         this.stopListeningFor(this.video, 'webkitendfullscreen', this.handleFullscreenChange);
47         this.stopListeningFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange);
48     },
49
50     createBase: function() {
51         Controller.prototype.createBase.call(this);
52
53         var startPlaybackButton = this.controls.startPlaybackButton = document.createElement('button');
54         startPlaybackButton.setAttribute('pseudo', '-webkit-media-controls-start-playback-button');
55         startPlaybackButton.setAttribute('aria-label', this.UIString('Start Playback'));
56
57         this.listenFor(this.base, 'gesturestart', this.handleBaseGestureStart);
58         this.listenFor(this.base, 'gesturechange', this.handleBaseGestureChange);
59         this.listenFor(this.base, 'gestureend', this.handleBaseGestureEnd);
60         this.listenFor(this.base, 'touchstart', this.handleWrapperTouchStart);
61         this.stopListeningFor(this.base, 'mousemove', this.handleWrapperMouseMove);
62         this.stopListeningFor(this.base, 'mouseout', this.handleWrapperMouseOut);
63
64         this.listenFor(document, 'visibilitychange', this.handleVisibilityChange);
65     },
66
67     shouldHaveStartPlaybackButton: function() {
68         var allowsInline = this.host.allowsInlineMediaPlayback;
69
70         if (this.isPlaying || (this.hasPlayed && allowsInline))
71             return false;
72
73         if (this.isAudio() && allowsInline)
74             return false;
75
76         if (this.doingSetup)
77             return true;
78
79         if (this.isFullScreen())
80             return false;
81
82         if (!this.video.currentSrc && this.video.error)
83             return false;
84
85         if (!this.video.controls && allowsInline)
86             return false;
87
88         if (this.video.currentSrc && this.video.error)
89             return true;
90
91         return true;
92     },
93
94     shouldHaveControls: function() {
95         if (this.shouldHaveStartPlaybackButton())
96             return false;
97
98         return Controller.prototype.shouldHaveControls.call(this);
99     },
100
101     shouldHaveAnyUI: function() {
102         return this.shouldHaveStartPlaybackButton() || Controller.prototype.shouldHaveAnyUI.call(this) || this.currentPlaybackTargetIsWireless();
103     },
104
105     createControls: function() {
106         Controller.prototype.createControls.call(this);
107
108         var panelContainer = this.controls.panelContainer = document.createElement('div');
109         panelContainer.setAttribute('pseudo', '-webkit-media-controls-panel-container');
110
111         var wirelessTargetPicker = this.controls.wirelessTargetPicker;
112         this.listenFor(wirelessTargetPicker, 'touchstart', this.handleWirelessPickerButtonTouchStart);
113         this.listenFor(wirelessTargetPicker, 'touchend', this.handleWirelessPickerButtonTouchEnd);
114         this.listenFor(wirelessTargetPicker, 'touchcancel', this.handleWirelessPickerButtonTouchCancel);
115
116         this.listenFor(this.controls.startPlaybackButton, 'touchstart', this.handleStartPlaybackButtonTouchStart);
117         this.listenFor(this.controls.startPlaybackButton, 'touchend', this.handleStartPlaybackButtonTouchEnd);
118         this.listenFor(this.controls.startPlaybackButton, 'touchcancel', this.handleStartPlaybackButtonTouchCancel);
119
120         this.listenFor(this.controls.panel, 'touchstart', this.handlePanelTouchStart);
121         this.listenFor(this.controls.panel, 'touchend', this.handlePanelTouchEnd);
122         this.listenFor(this.controls.panel, 'touchcancel', this.handlePanelTouchCancel);
123         this.listenFor(this.controls.playButton, 'touchstart', this.handlePlayButtonTouchStart);
124         this.listenFor(this.controls.playButton, 'touchend', this.handlePlayButtonTouchEnd);
125         this.listenFor(this.controls.playButton, 'touchcancel', this.handlePlayButtonTouchCancel);
126         this.listenFor(this.controls.fullscreenButton, 'touchstart', this.handleFullscreenTouchStart);
127         this.listenFor(this.controls.fullscreenButton, 'touchend', this.handleFullscreenTouchEnd);
128         this.listenFor(this.controls.fullscreenButton, 'touchcancel', this.handleFullscreenTouchCancel);
129         this.listenFor(this.controls.optimizedFullscreenButton, 'touchstart', this.handleOptimizedFullscreenTouchStart);
130         this.listenFor(this.controls.optimizedFullscreenButton, 'touchend', this.handleOptimizedFullscreenTouchEnd);
131         this.listenFor(this.controls.optimizedFullscreenButton, 'touchcancel', this.handleOptimizedFullscreenTouchCancel);
132         this.listenFor(this.controls.timeline, 'touchstart', this.handleTimelineTouchStart);
133         this.stopListeningFor(this.controls.playButton, 'click', this.handlePlayButtonClicked);
134
135         this.controls.timeline.style.backgroundImage = '-webkit-canvas(' + this.timelineContextName + ')';
136     },
137
138     setControlsType: function(type) {
139         if (type === this.controlsType)
140             return;
141         Controller.prototype.setControlsType.call(this, type);
142
143         if (type === ControllerIOS.StartPlaybackControls)
144             this.addStartPlaybackControls();
145         else
146             this.removeStartPlaybackControls();
147     },
148
149     addStartPlaybackControls: function() {
150         this.base.appendChild(this.controls.startPlaybackButton);
151     },
152
153     removeStartPlaybackControls: function() {
154         if (this.controls.startPlaybackButton.parentNode)
155             this.controls.startPlaybackButton.parentNode.removeChild(this.controls.startPlaybackButton);
156     },
157
158     reconnectControls: function()
159     {
160         Controller.prototype.reconnectControls.call(this);
161
162         if (this.controlsType === ControllerIOS.StartPlaybackControls)
163             this.addStartPlaybackControls();
164     },
165
166     configureInlineControls: function() {
167         this.controls.inlinePlaybackPlaceholder.appendChild(this.controls.inlinePlaybackPlaceholderText);
168         this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextTop);
169         this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextBottom);
170         this.controls.panel.appendChild(this.controls.playButton);
171         this.controls.panel.appendChild(this.controls.statusDisplay);
172         this.controls.panel.appendChild(this.controls.timelineBox);
173         this.controls.panel.appendChild(this.controls.wirelessTargetPicker);
174         if (!this.isLive) {
175             this.controls.timelineBox.appendChild(this.controls.currentTime);
176             this.controls.timelineBox.appendChild(this.controls.timeline);
177             this.controls.timelineBox.appendChild(this.controls.remainingTime);
178         }
179         if (this.isAudio()) {
180             // Hide the scrubber on audio until the user starts playing.
181             this.controls.timelineBox.classList.add(this.ClassNames.hidden);
182         } else {
183             if (Controller.gSimulateOptimizedFullscreenAvailable || ('webkitSupportsPresentationMode' in this.video && this.video.webkitSupportsPresentationMode('optimized')))
184                 this.controls.panel.appendChild(this.controls.optimizedFullscreenButton);
185             this.controls.panel.appendChild(this.controls.fullscreenButton);
186         }
187     },
188
189     configureFullScreenControls: function() {
190         // Explicitly do nothing to override base-class behavior.
191     },
192
193     controlsAreHidden: function()
194     {
195         // Controls are only ever actually hidden when they are removed from the tree
196         return !this.controls.panelContainer.parentElement;
197     },
198
199     addControls: function() {
200         this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
201         this.base.appendChild(this.controls.panelContainer);
202         this.controls.panelContainer.appendChild(this.controls.panelBackground);
203         this.controls.panelContainer.appendChild(this.controls.panel);
204         this.setNeedsTimelineMetricsUpdate();
205     },
206
207     updateControls: function() {
208         if (this.shouldHaveStartPlaybackButton())
209             this.setControlsType(ControllerIOS.StartPlaybackControls);
210         else if (this.presentationMode() === "fullscreen")
211             this.setControlsType(Controller.FullScreenControls);
212         else
213             this.setControlsType(Controller.InlineControls);
214
215         this.updateLayoutForDisplayedWidth();
216         this.setNeedsTimelineMetricsUpdate();
217     },
218
219     drawTimelineBackground: function() {
220         var width = this.timelineWidth * window.devicePixelRatio;
221         var height = this.timelineHeight * window.devicePixelRatio;
222
223         if (!width || !height)
224             return;
225
226         var played = this.video.currentTime / this.video.duration;
227         var buffered = 0;
228         var bufferedRanges = this.video.buffered;
229         if (bufferedRanges && bufferedRanges.length)
230             buffered = Math.max(bufferedRanges.end(bufferedRanges.length - 1), buffered);
231
232         buffered /= this.video.duration;
233         buffered = Math.max(buffered, played);
234
235         var ctx = document.getCSSCanvasContext('2d', this.timelineContextName, width, height);
236
237         ctx.clearRect(0, 0, width, height);
238
239         var midY = height / 2;
240
241         // 1. Draw the buffered part and played parts, using
242         // solid rectangles that are clipped to the outside of
243         // the lozenge.
244         ctx.save();
245         ctx.beginPath();
246         this.addRoundedRect(ctx, 1, midY - 3, width - 2, 6, 3);
247         ctx.closePath();
248         ctx.clip();
249         ctx.fillStyle = "white";
250         ctx.fillRect(0, 0, Math.round(width * played) + 2, height);
251         ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
252         ctx.fillRect(Math.round(width * played) + 2, 0, Math.round(width * (buffered - played)) + 2, height);
253         ctx.restore();
254
255         // 2. Draw the outline with a clip path that subtracts the
256         // middle of a lozenge. This produces a better result than
257         // stroking.
258         ctx.save();
259         ctx.beginPath();
260         this.addRoundedRect(ctx, 1, midY - 3, width - 2, 6, 3);
261         this.addRoundedRect(ctx, 2, midY - 2, width - 4, 4, 2);
262         ctx.closePath();
263         ctx.clip("evenodd");
264         ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
265         ctx.fillRect(Math.round(width * buffered) + 2, 0, width, height);
266         ctx.restore();
267     },
268
269     formatTime: function(time) {
270         if (isNaN(time))
271             time = 0;
272         var absTime = Math.abs(time);
273         var intSeconds = Math.floor(absTime % 60).toFixed(0);
274         var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
275         var intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
276         var sign = time < 0 ? '-' : String();
277
278         if (intHours > 0)
279             return sign + intHours + ':' + String('0' + intMinutes).slice(-2) + ":" + String('0' + intSeconds).slice(-2);
280
281         return sign + String('0' + intMinutes).slice(intMinutes >= 10 ? -2 : -1) + ":" + String('0' + intSeconds).slice(-2);
282     },
283
284     handlePlayButtonTouchStart: function() {
285         this.controls.playButton.classList.add('active');
286     },
287
288     handlePlayButtonTouchEnd: function(event) {
289         this.controls.playButton.classList.remove('active');
290
291         if (this.canPlay())
292             this.video.play();
293         else
294             this.video.pause();
295
296         return true;
297     },
298
299     handlePlayButtonTouchCancel: function(event) {
300         this.controls.playButton.classList.remove('active');
301         return true;
302     },
303
304     handleBaseGestureStart: function(event) {
305         this.gestureStartTime = new Date();
306         // If this gesture started with two fingers inside the video, then
307         // don't treat it as a potential zoom, unless we're still waiting
308         // to play.
309         if (this.mostRecentNumberOfTargettedTouches == 2 && this.controlsType != ControllerIOS.StartPlaybackControls)
310             event.preventDefault();
311     },
312
313     handleBaseGestureChange: function(event) {
314         if (!this.video.controls || this.isAudio() || this.isFullScreen() || this.gestureStartTime === undefined || this.controlsType == ControllerIOS.StartPlaybackControls)
315             return;
316
317         var scaleDetectionThreshold = 0.2;
318         if (event.scale > 1 + scaleDetectionThreshold || event.scale < 1 - scaleDetectionThreshold)
319             delete this.lastDoubleTouchTime;
320
321         if (this.mostRecentNumberOfTargettedTouches == 2 && event.scale >= 1.0)
322             event.preventDefault();
323
324         var currentGestureTime = new Date();
325         var duration = (currentGestureTime - this.gestureStartTime) / 1000;
326         if (!duration)
327             return;
328
329         var velocity = Math.abs(event.scale - 1) / duration;
330
331         var pinchOutVelocityThreshold = 2;
332         var pinchOutGestureScaleThreshold = 1.25;
333         if (velocity < pinchOutVelocityThreshold || event.scale < pinchOutGestureScaleThreshold)
334             return;
335
336         delete this.gestureStartTime;
337         this.video.webkitEnterFullscreen();
338     },
339
340     handleBaseGestureEnd: function(event) {
341         delete this.gestureStartTime;
342     },
343
344     handleWrapperTouchStart: function(event) {
345         if (event.target != this.base && event.target != this.controls.inlinePlaybackPlaceholder)
346             return;
347
348         this.mostRecentNumberOfTargettedTouches = event.targetTouches.length;
349
350         if (this.controlsAreHidden()) {
351             this.showControls();
352             if (this.hideTimer)
353                 clearTimeout(this.hideTimer);
354             this.hideTimer = setTimeout(this.hideControls.bind(this), this.HideControlsDelay);
355         } else if (!this.canPlay())
356             this.hideControls();
357
358         return true;
359     },
360
361     handlePanelTouchStart: function(event) {
362         this.video.style.webkitUserSelect = 'none';
363     },
364
365     handlePanelTouchEnd: function(event) {
366         this.video.style.removeProperty('-webkit-user-select');
367     },
368
369     handlePanelTouchCancel: function(event) {
370         this.video.style.removeProperty('-webkit-user-select');
371     },
372
373     handleVisibilityChange: function(event) {
374         this.updateShouldListenForPlaybackTargetAvailabilityEvent();
375     },
376
377     handlePanelTransitionEnd: function(event)
378     {
379         var opacity = window.getComputedStyle(this.controls.panel).opacity;
380         if (!parseInt(opacity) && !this.controlsAlwaysVisible()) {
381             this.base.removeChild(this.controls.inlinePlaybackPlaceholder);
382             this.base.removeChild(this.controls.panelContainer);
383         }
384     },
385
386     presentationMode: function() {
387         if ('webkitPresentationMode' in this.video)
388             return this.video.webkitPresentationMode;
389
390         if (this.isFullScreen())
391             return 'fullscreen';
392
393         return 'inline';
394     },
395
396     isFullScreen: function()
397     {
398         return this.video.webkitDisplayingFullscreen && this.presentationMode() != 'optimized';
399     },
400
401     handleFullscreenButtonClicked: function(event) {
402         if ('webkitSetPresentationMode' in this.video) {
403             if (this.presentationMode() === 'fullscreen')
404                 this.video.webkitSetPresentationMode('inline');
405             else
406                 this.video.webkitSetPresentationMode('fullscreen');
407
408             return;
409         }
410
411         if (this.isFullScreen())
412             this.video.webkitExitFullscreen();
413         else
414             this.video.webkitEnterFullscreen();
415     },
416
417     handleFullscreenTouchStart: function() {
418         this.controls.fullscreenButton.classList.add('active');
419     },
420
421     handleFullscreenTouchEnd: function(event) {
422         this.controls.fullscreenButton.classList.remove('active');
423
424         this.handleFullscreenButtonClicked();
425
426         return true;
427     },
428
429     handleFullscreenTouchCancel: function(event) {
430         this.controls.fullscreenButton.classList.remove('active');
431         return true;
432     },
433
434     handleOptimizedFullscreenButtonClicked: function(event) {
435         if (!('webkitSetPresentationMode' in this.video))
436             return;
437
438         if (this.presentationMode() === 'optimized')
439             this.video.webkitSetPresentationMode('inline');
440         else
441             this.video.webkitSetPresentationMode('optimized');
442     },
443
444     handleOptimizedFullscreenTouchStart: function() {
445         this.controls.optimizedFullscreenButton.classList.add('active');
446     },
447
448     handleOptimizedFullscreenTouchEnd: function(event) {
449         this.controls.optimizedFullscreenButton.classList.remove('active');
450
451         this.handleOptimizedFullscreenButtonClicked();
452
453         return true;
454     },
455
456     handleOptimizedFullscreenTouchCancel: function(event) {
457         this.controls.optimizedFullscreenButton.classList.remove('active');
458         return true;
459     },
460
461     handleStartPlaybackButtonTouchStart: function(event) {
462         this.controls.startPlaybackButton.classList.add('active');
463     },
464
465     handleStartPlaybackButtonTouchEnd: function(event) {
466         this.controls.startPlaybackButton.classList.remove('active');
467         if (this.video.error)
468             return true;
469
470         this.video.play();
471         this.updateControls();
472
473         return true;
474     },
475
476     handleStartPlaybackButtonTouchCancel: function(event) {
477         this.controls.startPlaybackButton.classList.remove('active');
478         return true;
479     },
480
481     handleTimelineTouchStart: function(event) {
482         this.scrubbing = true;
483         this.listenFor(this.controls.timeline, 'touchend', this.handleTimelineTouchEnd);
484         this.listenFor(this.controls.timeline, 'touchcancel', this.handleTimelineTouchEnd);
485     },
486
487     handleTimelineTouchEnd: function(event) {
488         this.stopListeningFor(this.controls.timeline, 'touchend', this.handleTimelineTouchEnd);
489         this.stopListeningFor(this.controls.timeline, 'touchcancel', this.handleTimelineTouchEnd);
490         this.scrubbing = false;
491     },
492
493     handleWirelessPickerButtonTouchStart: function() {
494         if (!this.video.error)
495             this.controls.wirelessTargetPicker.classList.add('active');
496     },
497
498     handleWirelessPickerButtonTouchEnd: function(event) {
499         this.controls.wirelessTargetPicker.classList.remove('active');
500         return this.handleWirelessPickerButtonClicked();
501     },
502
503     handleWirelessPickerButtonTouchCancel: function(event) {
504         this.controls.wirelessTargetPicker.classList.remove('active');
505         return true;
506     },
507
508     updateShouldListenForPlaybackTargetAvailabilityEvent: function() {
509         if (this.controlsType === ControllerIOS.StartPlaybackControls) {
510             this.setShouldListenForPlaybackTargetAvailabilityEvent(false);
511             return;
512         }
513
514         Controller.prototype.updateShouldListenForPlaybackTargetAvailabilityEvent.call(this);
515     },
516
517     updateWirelessTargetPickerButton: function() {
518     },
519
520     updateStatusDisplay: function(event)
521     {
522         this.controls.startPlaybackButton.classList.toggle(this.ClassNames.failed, this.video.error !== null);
523         Controller.prototype.updateStatusDisplay.call(this, event);
524     },
525
526     setPlaying: function(isPlaying)
527     {
528         Controller.prototype.setPlaying.call(this, isPlaying);
529
530         this.updateControls();
531
532         if (isPlaying && this.isAudio())
533             this.controls.timelineBox.classList.remove(this.ClassNames.hidden);
534
535         if (isPlaying)
536             this.hasPlayed = true;
537         else
538             this.showControls();
539     },
540
541     showControls: function()
542     {
543         this.updateShouldListenForPlaybackTargetAvailabilityEvent();
544         if (this.showInlinePlaybackPlaceholderOnly())
545             return;
546         
547         this.updateForShowingControls();
548         if (this.shouldHaveControls() && !this.controls.panelContainer.parentElement) {
549             this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
550             this.base.appendChild(this.controls.panelContainer);
551         }
552     },
553
554     setShouldListenForPlaybackTargetAvailabilityEvent: function(shouldListen)
555     {
556         if (shouldListen && (this.shouldHaveStartPlaybackButton() || this.video.error))
557             return;
558
559         Controller.prototype.setShouldListenForPlaybackTargetAvailabilityEvent.call(this, shouldListen);
560     },
561
562     handlePresentationModeChange: function(event)
563     {
564         var presentationMode = this.presentationMode();
565
566         switch (presentationMode) {
567             case 'inline':
568                 this.controls.inlinePlaybackPlaceholder.style.backgroundImage = "";
569                 this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden);
570                 this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.optimized);
571                 this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.optimized);
572                 this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.optimized);
573
574                 this.controls.optimizedFullscreenButton.classList.remove(this.ClassNames.returnFromOptimized);
575                 break;
576             case 'optimized':
577                 var backgroundImage = "url('" + this.host.mediaUIImageData("optimized-fullscreen-placeholder") + "')";
578                 this.controls.inlinePlaybackPlaceholder.style.backgroundImage = backgroundImage;
579                 this.controls.inlinePlaybackPlaceholder.setAttribute('aria-label', "video playback placeholder");
580                 this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.optimized);
581                 this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.hidden);
582
583                 this.controls.inlinePlaybackPlaceholderTextTop.innerText = this.host.mediaUIImageData("optimized-fullscreen-placeholder-text");
584                 this.controls.inlinePlaybackPlaceholderTextTop.classList.add(this.ClassNames.optimized);
585                 this.controls.inlinePlaybackPlaceholderTextBottom.innerText = "";
586                 this.controls.inlinePlaybackPlaceholderTextBottom.classList.add(this.ClassNames.optimized);
587
588                 this.controls.optimizedFullscreenButton.classList.add(this.ClassNames.returnFromOptimized);
589                 break;
590             default:
591                 this.controls.inlinePlaybackPlaceholder.style.backgroundImage = "";
592                 this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.optimized);
593                 this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.optimized);
594                 this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.optimized);
595
596                 this.controls.optimizedFullscreenButton.classList.remove(this.ClassNames.returnFromOptimized);
597                 break;
598         }
599
600         this.updateControls();
601         this.updateCaptionContainer();
602         if (presentationMode != 'fullscreen' && this.video.paused && this.controlsAreHidden())
603             this.showControls();
604     },
605
606     handleFullscreenChange: function(event)
607     {
608         Controller.prototype.handleFullscreenChange.call(this, event);
609         this.handlePresentationModeChange(event);
610     },
611
612     controlsAlwaysVisible: function()
613     {
614         if (this.presentationMode() === 'optimized')
615             return true;
616
617         return Controller.prototype.controlsAlwaysVisible.call(this);
618     },
619
620
621 };
622
623 Object.create(Controller.prototype).extend(ControllerIOS.prototype);
624 Object.defineProperty(ControllerIOS.prototype, 'constructor', { enumerable: false, value: ControllerIOS });