[Media iOS] Add a debug setting to always show the optimized fullscreen button
[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.hasWirelessPlaybackTargets = false;
10     this._pageScaleFactor = 1;
11     this.isListeningForPlaybackTargetAvailabilityEvent = false;
12     Controller.call(this, root, video, host);
13
14     this.updateWirelessTargetAvailable();
15     this.updateWirelessPlaybackStatus();
16     this.setNeedsTimelineMetricsUpdate();
17
18     host.controlsDependOnPageScaleFactor = true;
19     this.doingSetup = false;
20 };
21
22 /* Enums */
23 ControllerIOS.StartPlaybackControls = 2;
24
25 /* Globals */
26 ControllerIOS.gWirelessImage = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 245"><g fill="#1060FE"><path d="M193.6,6.3v121.6H6.4V6.3H193.6 M199.1,0.7H0.9v132.7h198.2V0.7L199.1,0.7z"/><path d="M43.5,139.3c15.8,8,35.3,12.7,56.5,12.7s40.7-4.7,56.5-12.7H43.5z"/></g><g text-anchor="middle" font-family="Helvetica Neue"><text x="100" y="204" fill="white" font-size="24">##DEVICE_TYPE##</text><text x="100" y="234" fill="#5C5C5C" font-size="21">##DEVICE_NAME##</text></g></svg>';
27 ControllerIOS.gSimulateWirelessPlaybackTarget = false; // Used for testing when there are no wireless targets.
28 ControllerIOS.gSimulateOptimizedFullscreenAvailable = false; // Used for testing when optimized fullscreen is not available.
29
30 ControllerIOS.prototype = {
31     addVideoListeners: function() {
32         Controller.prototype.addVideoListeners.call(this);
33
34         this.listenFor(this.video, 'webkitbeginfullscreen', this.handleFullscreenChange);
35         this.listenFor(this.video, 'webkitendfullscreen', this.handleFullscreenChange);
36         this.listenFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange);
37         this.listenFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange);
38     },
39
40     removeVideoListeners: function() {
41         Controller.prototype.removeVideoListeners.call(this);
42
43         this.stopListeningFor(this.video, 'webkitbeginfullscreen', this.handleFullscreenChange);
44         this.stopListeningFor(this.video, 'webkitendfullscreen', this.handleFullscreenChange);
45         this.stopListeningFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange);
46         this.stopListeningFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange);
47
48         this.setShouldListenForPlaybackTargetAvailabilityEvent(false);
49     },
50
51     createBase: function() {
52         Controller.prototype.createBase.call(this);
53
54         var startPlaybackButton = this.controls.startPlaybackButton = document.createElement('button');
55         startPlaybackButton.setAttribute('pseudo', '-webkit-media-controls-start-playback-button');
56         startPlaybackButton.setAttribute('aria-label', this.UIString('Start Playback'));
57
58         this.listenFor(this.base, 'gesturestart', this.handleBaseGestureStart);
59         this.listenFor(this.base, 'gesturechange', this.handleBaseGestureChange);
60         this.listenFor(this.base, 'gestureend', this.handleBaseGestureEnd);
61         this.listenFor(this.base, 'touchstart', this.handleWrapperTouchStart);
62         this.stopListeningFor(this.base, 'mousemove', this.handleWrapperMouseMove);
63         this.stopListeningFor(this.base, 'mouseout', this.handleWrapperMouseOut);
64
65         this.listenFor(document, 'visibilitychange', this.handleVisibilityChange);
66     },
67
68     shouldHaveStartPlaybackButton: function() {
69         var allowsInline = this.host.mediaPlaybackAllowsInline;
70
71         if (this.isPlaying)
72             return false;
73
74         if (this.isAudio() && allowsInline)
75             return false;
76
77         if (this.isFullScreen())
78             return false;
79
80         if (!this.video.currentSrc && this.video.error)
81             return false;
82
83         if (!this.video.controls && allowsInline)
84             return false;
85
86         if (this.video.currentSrc && this.video.error)
87             return true;
88
89         if (!this.doingSetup && !this.host.userGestureRequired && allowsInline)
90             return false;
91
92         return true;
93     },
94
95     shouldHaveControls: function() {
96         if (this.shouldHaveStartPlaybackButton())
97             return false;
98
99         return Controller.prototype.shouldHaveControls.call(this);
100     },
101
102     shouldHaveAnyUI: function() {
103         return this.shouldHaveStartPlaybackButton() || Controller.prototype.shouldHaveAnyUI.call(this) || this.currentPlaybackTargetIsWireless();
104     },
105
106     currentPlaybackTargetIsWireless: function() {
107         return ControllerIOS.gSimulateWirelessPlaybackTarget || (('webkitCurrentPlaybackTargetIsWireless' in this.video) && this.video.webkitCurrentPlaybackTargetIsWireless);
108     },
109
110     updateWirelessPlaybackStatus: function() {
111         if (this.currentPlaybackTargetIsWireless()) {
112             var backgroundImageSVG = "url('" + ControllerIOS.gWirelessImage + "')";
113
114             var deviceName = "";
115             var deviceType = "";
116             var type = this.host.externalDeviceType;
117             if (type == "airplay") {
118                 deviceType = this.UIString('##WIRELESS_PLAYBACK_DEVICE_TYPE##');
119                 deviceName = this.UIString('##WIRELESS_PLAYBACK_DEVICE_NAME##', '##DEVICE_NAME##', this.host.externalDeviceDisplayName || "Apple TV");
120             } else if (type == "tvout") {
121                 deviceType = this.UIString('##TVOUT_DEVICE_TYPE##');
122                 deviceName = this.UIString('##TVOUT_DEVICE_NAME##');
123             }
124
125             backgroundImageSVG = backgroundImageSVG.replace('##DEVICE_TYPE##', deviceType);
126             backgroundImageSVG = backgroundImageSVG.replace('##DEVICE_NAME##', deviceName);
127
128             this.controls.inlinePlaybackPlaceholder.style.backgroundImage = backgroundImageSVG;
129             this.controls.inlinePlaybackPlaceholder.setAttribute('aria-label', deviceType + ", " + deviceName);
130
131             this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.hidden);
132             this.controls.wirelessTargetPicker.classList.add(this.ClassNames.active);
133         } else {
134             this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden);
135             this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.active);
136         }
137     },
138
139     updateWirelessTargetAvailable: function() {
140         if (ControllerIOS.gSimulateWirelessPlaybackTarget || this.hasWirelessPlaybackTargets)
141             this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.hidden);
142         else
143             this.controls.wirelessTargetPicker.classList.add(this.ClassNames.hidden);
144     },
145
146     createControls: function() {
147         Controller.prototype.createControls.call(this);
148
149         var panelCompositedParent = this.controls.panelCompositedParent = document.createElement('div');
150         panelCompositedParent.setAttribute('pseudo', '-webkit-media-controls-panel-composited-parent');
151
152         var inlinePlaybackPlaceholder = this.controls.inlinePlaybackPlaceholder = document.createElement('div');
153         inlinePlaybackPlaceholder.setAttribute('pseudo', '-webkit-media-controls-inline-playback-placeholder');
154         if (!ControllerIOS.gSimulateOptimizedFullscreenAvailable)
155             inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden);
156         inlinePlaybackPlaceholder.setAttribute('aria-label', this.UIString('Display Optimized Full Screen'));
157
158         var wirelessTargetPicker = this.controls.wirelessTargetPicker = document.createElement('button');
159         wirelessTargetPicker.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-picker-button');
160         wirelessTargetPicker.setAttribute('aria-label', this.UIString('Choose Wireless Display'));
161         this.listenFor(wirelessTargetPicker, 'touchstart', this.handleWirelessPickerButtonTouchStart);
162         this.listenFor(wirelessTargetPicker, 'touchend', this.handleWirelessPickerButtonTouchEnd);
163         this.listenFor(wirelessTargetPicker, 'touchcancel', this.handleWirelessPickerButtonTouchCancel);
164
165         if (!ControllerIOS.gSimulateWirelessPlaybackTarget)
166             wirelessTargetPicker.classList.add(this.ClassNames.hidden);
167
168         this.listenFor(this.controls.startPlaybackButton, 'touchstart', this.handleStartPlaybackButtonTouchStart);
169         this.listenFor(this.controls.startPlaybackButton, 'touchend', this.handleStartPlaybackButtonTouchEnd);
170         this.listenFor(this.controls.startPlaybackButton, 'touchcancel', this.handleStartPlaybackButtonTouchCancel);
171
172         this.listenFor(this.controls.panel, 'touchstart', this.handlePanelTouchStart);
173         this.listenFor(this.controls.panel, 'touchend', this.handlePanelTouchEnd);
174         this.listenFor(this.controls.panel, 'touchcancel', this.handlePanelTouchCancel);
175         this.listenFor(this.controls.playButton, 'touchstart', this.handlePlayButtonTouchStart);
176         this.listenFor(this.controls.playButton, 'touchend', this.handlePlayButtonTouchEnd);
177         this.listenFor(this.controls.playButton, 'touchcancel', this.handlePlayButtonTouchCancel);
178         this.listenFor(this.controls.fullscreenButton, 'touchstart', this.handleFullscreenTouchStart);
179         this.listenFor(this.controls.fullscreenButton, 'touchend', this.handleFullscreenTouchEnd);
180         this.listenFor(this.controls.fullscreenButton, 'touchcancel', this.handleFullscreenTouchCancel);
181         this.listenFor(this.controls.optimizedFullscreenButton, 'touchstart', this.handleOptimizedFullscreenTouchStart);
182         this.listenFor(this.controls.optimizedFullscreenButton, 'touchend', this.handleOptimizedFullscreenTouchEnd);
183         this.listenFor(this.controls.optimizedFullscreenButton, 'touchcancel', this.handleOptimizedFullscreenTouchCancel);
184         this.stopListeningFor(this.controls.playButton, 'click', this.handlePlayButtonClicked);
185     },
186
187     setControlsType: function(type) {
188         if (type === this.controlsType)
189             return;
190         Controller.prototype.setControlsType.call(this, type);
191
192         if (type === ControllerIOS.StartPlaybackControls)
193             this.addStartPlaybackControls();
194         else
195             this.removeStartPlaybackControls();
196
197         this.updateShouldListenForPlaybackTargetAvailabilityEvent();
198     },
199
200     addStartPlaybackControls: function() {
201         this.base.appendChild(this.controls.startPlaybackButton);
202     },
203
204     removeStartPlaybackControls: function() {
205         if (this.controls.startPlaybackButton.parentNode)
206             this.controls.startPlaybackButton.parentNode.removeChild(this.controls.startPlaybackButton);
207     },
208
209     reconnectControls: function()
210     {
211         Controller.prototype.reconnectControls.call(this);
212
213         if (this.controlsType === ControllerIOS.StartPlaybackControls)
214             this.addStartPlaybackControls();
215     },
216
217     configureInlineControls: function() {
218         this.controls.panel.appendChild(this.controls.playButton);
219         this.controls.panel.appendChild(this.controls.statusDisplay);
220         this.controls.panel.appendChild(this.controls.timelineBox);
221         this.controls.panel.appendChild(this.controls.wirelessTargetPicker);
222         if (!this.isLive) {
223             this.controls.timelineBox.appendChild(this.controls.currentTime);
224             this.controls.timelineBox.appendChild(this.controls.timeline);
225             this.controls.timelineBox.appendChild(this.controls.remainingTime);
226         }
227         if (!this.isAudio()) {
228             if (ControllerIOS.gSimulateOptimizedFullscreenAvailable || ('webkitSupportsPresentationMode' in this.video && this.video.webkitSupportsPresentationMode('optimized')))
229                 this.controls.panel.appendChild(this.controls.optimizedFullscreenButton);
230             this.controls.panel.appendChild(this.controls.fullscreenButton);
231         }
232     },
233
234     configureFullScreenControls: function() {
235         // Do nothing
236     },
237
238     hideControls: function() {
239         Controller.prototype.hideControls.call(this);
240         this.updateShouldListenForPlaybackTargetAvailabilityEvent();
241     },
242
243     showControls: function() {
244         Controller.prototype.showControls.call(this);
245         this.updateShouldListenForPlaybackTargetAvailabilityEvent();
246     },
247
248     addControls: function() {
249         this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
250         this.base.appendChild(this.controls.panelCompositedParent);
251         this.controls.panelCompositedParent.appendChild(this.controls.panel);
252         this.setNeedsTimelineMetricsUpdate();
253     },
254
255     updateControls: function() {
256         if (this.shouldHaveStartPlaybackButton())
257             this.setControlsType(ControllerIOS.StartPlaybackControls);
258         else if (this.presentationMode() === "fullscreen")
259             this.setControlsType(Controller.FullScreenControls);
260         else
261             this.setControlsType(Controller.InlineControls);
262
263         this.setNeedsTimelineMetricsUpdate();
264     },
265
266     updateTime: function() {
267         Controller.prototype.updateTime.call(this);
268         this.updateProgress();
269     },
270
271     progressFillStyle: function() {
272         return 'rgba(0, 0, 0, 0.5)';
273     },
274
275     updateProgress: function(forceUpdate) {
276         Controller.prototype.updateProgress.call(this, forceUpdate);
277
278         if (!forceUpdate && this.controlsAreHidden())
279             return;
280
281         var width = this.timelineWidth;
282         var height = this.timelineHeight;
283
284         // Magic number, matching the value for ::-webkit-media-controls-timeline::-webkit-slider-thumb
285         // in mediaControlsiOS.css. Since we cannot ask the thumb for its offsetWidth as it's in its own
286         // shadow dom, just hard-code the value.
287         var thumbWidth = 16;
288         var endX = thumbWidth / 2 + (width - thumbWidth) * this.video.currentTime / this.video.duration;
289
290         var context = document.getCSSCanvasContext('2d', 'timeline-' + this.timelineID, width, height);
291         context.fillStyle = 'white';
292         context.fillRect(0, 0, endX, height);
293     },
294
295     formatTime: function(time) {
296         if (isNaN(time))
297             time = 0;
298         var absTime = Math.abs(time);
299         var intSeconds = Math.floor(absTime % 60).toFixed(0);
300         var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
301         var intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
302         var sign = time < 0 ? '-' : String();
303
304         if (intHours > 0)
305             return sign + intHours + ':' + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2);
306
307         return sign + String('00' + intMinutes).slice(intMinutes >= 10 ? -2 : -1) + ":" + String('00' + intSeconds).slice(-2);
308     },
309
310     handleTimelineChange: function(event) {
311         Controller.prototype.handleTimelineChange.call(this);
312         this.updateProgress();
313     },
314
315     handlePlayButtonTouchStart: function() {
316         this.controls.playButton.classList.add('active');
317     },
318
319     handlePlayButtonTouchEnd: function(event) {
320         this.controls.playButton.classList.remove('active');
321
322         if (this.canPlay())
323             this.video.play();
324         else
325             this.video.pause();
326
327         return true;
328     },
329
330     handlePlayButtonTouchCancel: function(event) {
331         this.controls.playButton.classList.remove('active');
332         return true;
333     },
334
335     handleBaseGestureStart: function(event) {
336         this.gestureStartTime = new Date();
337         // If this gesture started with two fingers inside the video, then
338         // don't treat it as a potential zoom, unless we're still waiting
339         // to play.
340         if (this.mostRecentNumberOfTargettedTouches == 2 && this.controlsType != ControllerIOS.StartPlaybackControls)
341             event.preventDefault();
342     },
343
344     handleBaseGestureChange: function(event) {
345         if (!this.video.controls || this.isAudio() || this.isFullScreen() || this.gestureStartTime === undefined || this.controlsType == ControllerIOS.StartPlaybackControls)
346             return;
347
348         var scaleDetectionThreshold = 0.2;
349         if (event.scale > 1 + scaleDetectionThreshold || event.scale < 1 - scaleDetectionThreshold)
350             delete this.lastDoubleTouchTime;
351
352         if (this.mostRecentNumberOfTargettedTouches == 2 && event.scale >= 1.0)
353             event.preventDefault();
354
355         var currentGestureTime = new Date();
356         var duration = (currentGestureTime - this.gestureStartTime) / 1000;
357         if (!duration)
358             return;
359
360         var velocity = Math.abs(event.scale - 1) / duration;
361
362         var pinchOutVelocityThreshold = 2;
363         var pinchOutGestureScaleThreshold = 1.25;
364         if (velocity < pinchOutVelocityThreshold || event.scale < pinchOutGestureScaleThreshold)
365             return;
366
367         delete this.gestureStartTime;
368         this.video.webkitEnterFullscreen();
369     },
370
371     handleBaseGestureEnd: function(event) {
372         delete this.gestureStartTime;
373     },
374
375     handleWrapperTouchStart: function(event) {
376         if (event.target != this.base && event.target != this.controls.inlinePlaybackPlaceholder)
377             return;
378
379         this.mostRecentNumberOfTargettedTouches = event.targetTouches.length;
380
381         if (this.controlsAreHidden()) {
382             this.showControls();
383             if (this.hideTimer)
384                 clearTimeout(this.hideTimer);
385             this.hideTimer = setTimeout(this.hideControls.bind(this), this.HideControlsDelay);
386         } else if (!this.canPlay())
387             this.hideControls();
388     },
389
390     handlePanelTouchStart: function(event) {
391         this.video.style.webkitUserSelect = 'none';
392     },
393
394     handlePanelTouchEnd: function(event) {
395         this.video.style.removeProperty('-webkit-user-select');
396     },
397
398     handlePanelTouchCancel: function(event) {
399         this.video.style.removeProperty('-webkit-user-select');
400     },
401
402     handleVisibilityChange: function(event) {
403         this.updateShouldListenForPlaybackTargetAvailabilityEvent();
404     },
405
406     presentationMode: function() {
407         if ('webkitPresentationMode' in this.video)
408             return this.video.webkitPresentationMode;
409
410         if (this.isFullScreen())
411             return 'fullscreen';
412
413         return 'inline';
414     },
415
416     isFullScreen: function()
417     {
418         return this.video.webkitDisplayingFullscreen && this.presentationMode() != 'optimized';
419     },
420
421     handleFullscreenButtonClicked: function(event) {
422         if ('webkitSetPresentationMode' in this.video) {
423             if (this.presentationMode() === 'fullscreen')
424                 this.video.webkitSetPresentationMode('inline');
425             else
426                 this.video.webkitSetPresentationMode('fullscreen');
427
428             return;
429         }
430
431         if (this.isFullScreen())
432             this.video.webkitExitFullscreen();
433         else
434             this.video.webkitEnterFullscreen();
435     },
436
437     handleFullscreenTouchStart: function() {
438         this.controls.fullscreenButton.classList.add('active');
439     },
440
441     handleFullscreenTouchEnd: function(event) {
442         this.controls.fullscreenButton.classList.remove('active');
443
444         this.handleFullscreenButtonClicked();
445
446         return true;
447     },
448
449     handleFullscreenTouchCancel: function(event) {
450         this.controls.fullscreenButton.classList.remove('active');
451         return true;
452     },
453
454     handleOptimizedFullscreenButtonClicked: function(event) {
455         if (!('webkitSetPresentationMode' in this.video))
456             return;
457
458         if (this.presentationMode() === 'optimized')
459             this.video.webkitSetPresentationMode('inline');
460         else
461             this.video.webkitSetPresentationMode('optimized');
462     },
463
464     handleOptimizedFullscreenTouchStart: function() {
465         this.controls.optimizedFullscreenButton.classList.add('active');
466     },
467
468     handleOptimizedFullscreenTouchEnd: function(event) {
469         this.controls.optimizedFullscreenButton.classList.remove('active');
470
471         this.handleOptimizedFullscreenButtonClicked();
472
473         return true;
474     },
475
476     handleOptimizedFullscreenTouchCancel: function(event) {
477         this.controls.optimizedFullscreenButton.classList.remove('active');
478         return true;
479     },
480
481     handleStartPlaybackButtonTouchStart: function(event) {
482         this.controls.startPlaybackButton.classList.add('active');
483     },
484
485     handleStartPlaybackButtonTouchEnd: function(event) {
486         this.controls.startPlaybackButton.classList.remove('active');
487         if (this.video.error)
488             return true;
489
490         this.video.play();
491         this.updateControls();
492
493         return true;
494     },
495
496     handleStartPlaybackButtonTouchCancel: function(event) {
497         this.controls.startPlaybackButton.classList.remove('active');
498         return true;
499     },
500
501     handleReadyStateChange: function(event) {
502         Controller.prototype.handleReadyStateChange.call(this, event);
503         this.updateControls();
504     },
505
506     handleWirelessPlaybackChange: function(event) {
507         this.updateWirelessPlaybackStatus();
508         this.setNeedsTimelineMetricsUpdate();
509     },
510
511     handleWirelessTargetAvailableChange: function(event) {
512         var wirelessPlaybackTargetsAvailable = event.availability == "available";
513         if (this.hasWirelessPlaybackTargets === wirelessPlaybackTargetsAvailable)
514             return;
515
516         this.hasWirelessPlaybackTargets = wirelessPlaybackTargetsAvailable;
517         this.updateWirelessTargetAvailable();
518         this.setNeedsTimelineMetricsUpdate();
519     },
520
521     handleWirelessPickerButtonTouchStart: function() {
522         if (!this.video.error)
523             this.controls.wirelessTargetPicker.classList.add('active');
524     },
525
526     handleWirelessPickerButtonTouchEnd: function(event) {
527         this.controls.wirelessTargetPicker.classList.remove('active');
528         this.video.webkitShowPlaybackTargetPicker();
529         return true;
530     },
531
532     handleWirelessPickerButtonTouchCancel: function(event) {
533         this.controls.wirelessTargetPicker.classList.remove('active');
534         return true;
535     },
536
537     updateShouldListenForPlaybackTargetAvailabilityEvent: function() {
538         var shouldListen = true;
539         if (this.video.error)
540             shouldListen = false;
541         if (this.controlsType === ControllerIOS.StartPlaybackControls)
542             shouldListen = false;
543         if (!this.isAudio() && !this.video.paused && this.controlsAreHidden())
544             shouldListen = false;
545         if (document.hidden)
546             shouldListen = false;
547
548         this.setShouldListenForPlaybackTargetAvailabilityEvent(shouldListen);
549     },
550
551     updateStatusDisplay: function(event)
552     {
553         this.updateShouldListenForPlaybackTargetAvailabilityEvent();
554         this.controls.startPlaybackButton.classList.toggle(this.ClassNames.failed, this.video.error !== null);
555         Controller.prototype.updateStatusDisplay.call(this, event);
556     },
557
558     setPlaying: function(isPlaying)
559     {
560         Controller.prototype.setPlaying.call(this, isPlaying);
561         this.updateControls();
562     },
563
564     setShouldListenForPlaybackTargetAvailabilityEvent: function(shouldListen)
565     {
566         if (!window.WebKitPlaybackTargetAvailabilityEvent || this.isListeningForPlaybackTargetAvailabilityEvent == shouldListen)
567             return;
568
569         if (shouldListen && (this.shouldHaveStartPlaybackButton() || this.video.error))
570             return;
571
572         this.isListeningForPlaybackTargetAvailabilityEvent = shouldListen;
573         if (shouldListen)
574             this.listenFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange);
575         else
576             this.stopListeningFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange);
577     },
578
579     get pageScaleFactor()
580     {
581         return this._pageScaleFactor;
582     },
583
584     set pageScaleFactor(newScaleFactor)
585     {
586         if (this._pageScaleFactor === newScaleFactor)
587             return;
588
589         this._pageScaleFactor = newScaleFactor;
590
591         if (newScaleFactor) {
592             var scaleValue = 1 / newScaleFactor;
593             var scaleTransform = "scale(" + scaleValue + ")";
594             if (this.controls.startPlaybackButton)
595                 this.controls.startPlaybackButton.style.webkitTransform = scaleTransform;
596             if (this.controls.panel) {
597                 var bottomAligment = -2 * scaleValue;
598                 this.controls.panel.style.bottom = bottomAligment + "px";
599                 this.controls.panel.style.paddingBottom = -(newScaleFactor * bottomAligment) + "px";
600                 this.controls.panel.style.width = Math.ceil(newScaleFactor * 100) + "%";
601                 this.controls.panel.style.webkitTransform = scaleTransform;
602                 this.setNeedsTimelineMetricsUpdate();
603                 this.updateProgress();
604             }
605         }
606     },
607
608     handlePresentationModeChange: function(event)
609     {
610         var presentationMode = this.presentationMode();
611
612         switch (presentationMode) {
613             case 'inline':
614                 this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden);
615                 break;
616             case 'optimized':
617                 var backgroundImage = "url('" + this.host.mediaUIImageData("optimized-fullscreen-placeholder") + "')";
618                 this.controls.inlinePlaybackPlaceholder.style.backgroundImage = backgroundImage;
619                 this.controls.inlinePlaybackPlaceholder.setAttribute('aria-label', "video playback placeholder");
620                 this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.hidden);
621                 break;
622         }
623
624         this.updateControls();
625         this.updateCaptionContainer();
626         if (presentationMode != 'fullscreen' && this.video.paused && this.controlsAreHidden())
627             this.showControls();
628     },
629
630     handleFullscreenChange: function(event)
631     {
632         Controller.prototype.handleFullscreenChange.call(this, event);
633         this.handlePresentationModeChange(event);
634     },
635
636 };
637
638 Object.create(Controller.prototype).extend(ControllerIOS.prototype);
639 Object.defineProperty(ControllerIOS.prototype, 'constructor', { enumerable: false, value: ControllerIOS });