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