Remove unnecessary `const double` method arguments
[WebKit-https.git] / Source / WebCore / Modules / mediacontrols / mediaControlsiOS.js
index 44eae18..9d6d28a 100644 (file)
@@ -5,55 +5,53 @@ function createControls(root, video, host)
 
 function ControllerIOS(root, video, host)
 {
-    this.hasWirelessPlaybackTargets = false;
+    this.doingSetup = true;
     this._pageScaleFactor = 1;
+
+    this.timelineContextName = "_webkit-media-controls-timeline-" + host.generateUUID();
+
     Controller.call(this, root, video, host);
 
-    this.updateWirelessTargetAvailable();
-    this.updateWirelessPlaybackStatus();
     this.setNeedsTimelineMetricsUpdate();
 
+    this._timelineIsHidden = false;
+    this._currentDisplayWidth = 0;
+    this.scheduleUpdateLayoutForDisplayedWidth();
+
     host.controlsDependOnPageScaleFactor = true;
+    this.doingSetup = false;
 };
 
 /* Enums */
 ControllerIOS.StartPlaybackControls = 2;
 
-/* Globals */
-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>';
-ControllerIOS.gSimulateWirelessPlaybackTarget = false; // Used for testing when there are no wireless targets.
-
 ControllerIOS.prototype = {
-    addVideoListeners: function() {
-        Controller.prototype.addVideoListeners.call(this);
-
-        this.listenFor(this.video, 'webkitbeginfullscreen', this.handleFullscreenChange);
-        this.listenFor(this.video, 'webkitendfullscreen', this.handleFullscreenChange);
-
-        if (window.WebKitPlaybackTargetAvailabilityEvent) {
-            this.listenFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange);
-            this.listenFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange);
-        }
-    },
-
-    removeVideoListeners: function() {
-        Controller.prototype.removeVideoListeners.call(this);
+    /* Constants */
+    MinimumTimelineWidth: 150,
+    ButtonWidth: 42,
 
-        this.stopListeningFor(this.video, 'webkitbeginfullscreen', this.handleFullscreenChange);
-        this.stopListeningFor(this.video, 'webkitendfullscreen', this.handleFullscreenChange);
-
-        if (window.WebKitPlaybackTargetAvailabilityEvent) {
-            this.stopListeningFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange);
-            this.stopListeningFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange);
-        }
+    get idiom()
+    {
+        return "ios";
     },
 
     createBase: function() {
         Controller.prototype.createBase.call(this);
 
-        var startPlaybackButton = this.controls.startPlaybackButton = document.createElement('button');
+        var startPlaybackButton = this.controls.startPlaybackButton = document.createElement('div');
         startPlaybackButton.setAttribute('pseudo', '-webkit-media-controls-start-playback-button');
         startPlaybackButton.setAttribute('aria-label', this.UIString('Start Playback'));
+        startPlaybackButton.setAttribute('role', 'button');
+
+        var startPlaybackBackground = document.createElement('div');
+        startPlaybackBackground.setAttribute('pseudo', '-webkit-media-controls-start-playback-background');
+        startPlaybackBackground.classList.add('webkit-media-controls-start-playback-background');
+        startPlaybackButton.appendChild(startPlaybackBackground);
+
+        var startPlaybackGlyph = document.createElement('div');
+        startPlaybackGlyph.setAttribute('pseudo', '-webkit-media-controls-start-playback-glyph');
+        startPlaybackGlyph.classList.add('webkit-media-controls-start-playback-glyph');
+        startPlaybackButton.appendChild(startPlaybackGlyph);
 
         this.listenFor(this.base, 'gesturestart', this.handleBaseGestureStart);
         this.listenFor(this.base, 'gesturechange', this.handleBaseGestureChange);
@@ -61,14 +59,22 @@ ControllerIOS.prototype = {
         this.listenFor(this.base, 'touchstart', this.handleWrapperTouchStart);
         this.stopListeningFor(this.base, 'mousemove', this.handleWrapperMouseMove);
         this.stopListeningFor(this.base, 'mouseout', this.handleWrapperMouseOut);
+
+        this.listenFor(document, 'visibilitychange', this.handleVisibilityChange);
     },
 
     shouldHaveStartPlaybackButton: function() {
-        var allowsInline = this.host.mediaPlaybackAllowsInline;
+        var allowsInline = this.host.allowsInlineMediaPlayback;
+
+        if (this.isPlaying || (this.hasPlayed && allowsInline))
+            return false;
 
         if (this.isAudio() && allowsInline)
             return false;
 
+        if (this.doingSetup)
+            return true;
+
         if (this.isFullScreen())
             return false;
 
@@ -81,9 +87,6 @@ ControllerIOS.prototype = {
         if (this.video.currentSrc && this.video.error)
             return true;
 
-        if (!this.host.userGestureRequired && allowsInline)
-            return false;
-
         return true;
     },
 
@@ -98,63 +101,17 @@ ControllerIOS.prototype = {
         return this.shouldHaveStartPlaybackButton() || Controller.prototype.shouldHaveAnyUI.call(this) || this.currentPlaybackTargetIsWireless();
     },
 
-    currentPlaybackTargetIsWireless: function() {
-        return ControllerIOS.gSimulateWirelessPlaybackTarget || (('webkitCurrentPlaybackTargetIsWireless' in this.video) && this.video.webkitCurrentPlaybackTargetIsWireless);
-    },
-
-    updateWirelessPlaybackStatus: function() {
-        if (this.currentPlaybackTargetIsWireless()) {
-            var backgroundImageSVG = "url('" + ControllerIOS.gWirelessImage + "')";
-
-            var deviceName = "";
-            var deviceType = "";
-            var type = this.host.externalDeviceType;
-            if (type == "airplay") {
-                deviceType = this.UIString('##WIRELESS_PLAYBACK_DEVICE_TYPE##');
-                deviceName = this.UIString('##WIRELESS_PLAYBACK_DEVICE_NAME##', '##DEVICE_NAME##', this.host.externalDeviceDisplayName);
-            } else if (type == "tvout") {
-                deviceType = this.UIString('##TVOUT_DEVICE_TYPE##');
-                deviceName = this.UIString('##TVOUT_DEVICE_NAME##');
-            }
-            
-            backgroundImageSVG = backgroundImageSVG.replace('##DEVICE_TYPE##', deviceType);
-            backgroundImageSVG = backgroundImageSVG.replace('##DEVICE_NAME##', deviceName);
-
-            this.controls.wirelessPlaybackStatus.style.backgroundImage = backgroundImageSVG;
-            this.controls.wirelessPlaybackStatus.setAttribute('aria-label', deviceType + ", " + deviceName);
-
-            this.controls.wirelessPlaybackStatus.classList.remove(this.ClassNames.hidden);
-            this.controls.wirelessTargetPicker.classList.add(this.ClassNames.active);
-        } else {
-            this.controls.wirelessPlaybackStatus.classList.add(this.ClassNames.hidden);
-            this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.active);
-        }
-    },
-
-    updateWirelessTargetAvailable: function() {
-        if (ControllerIOS.gSimulateWirelessPlaybackTarget || this.hasWirelessPlaybackTargets)
-            this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.hidden);
-        else
-            this.controls.wirelessTargetPicker.classList.add(this.ClassNames.hidden);
-    },
-
     createControls: function() {
         Controller.prototype.createControls.call(this);
 
-        var wirelessPlaybackStatus = this.controls.wirelessPlaybackStatus = document.createElement('div');
-        wirelessPlaybackStatus.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-status');
-        wirelessPlaybackStatus.classList.add(this.ClassNames.hidden);
+        var panelContainer = this.controls.panelContainer = document.createElement('div');
+        panelContainer.setAttribute('pseudo', '-webkit-media-controls-panel-container');
 
-        var wirelessTargetPicker = this.controls.wirelessTargetPicker = document.createElement('button');
-        wirelessTargetPicker.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-picker-button');
-        wirelessTargetPicker.setAttribute('aria-label', this.UIString('Choose Wireless Display'));
+        var wirelessTargetPicker = this.controls.wirelessTargetPicker;
         this.listenFor(wirelessTargetPicker, 'touchstart', this.handleWirelessPickerButtonTouchStart);
         this.listenFor(wirelessTargetPicker, 'touchend', this.handleWirelessPickerButtonTouchEnd);
         this.listenFor(wirelessTargetPicker, 'touchcancel', this.handleWirelessPickerButtonTouchCancel);
 
-        if (!ControllerIOS.gSimulateWirelessPlaybackTarget)
-            wirelessTargetPicker.classList.add(this.ClassNames.hidden);
-
         this.listenFor(this.controls.startPlaybackButton, 'touchstart', this.handleStartPlaybackButtonTouchStart);
         this.listenFor(this.controls.startPlaybackButton, 'touchend', this.handleStartPlaybackButtonTouchEnd);
         this.listenFor(this.controls.startPlaybackButton, 'touchcancel', this.handleStartPlaybackButtonTouchCancel);
@@ -168,7 +125,13 @@ ControllerIOS.prototype = {
         this.listenFor(this.controls.fullscreenButton, 'touchstart', this.handleFullscreenTouchStart);
         this.listenFor(this.controls.fullscreenButton, 'touchend', this.handleFullscreenTouchEnd);
         this.listenFor(this.controls.fullscreenButton, 'touchcancel', this.handleFullscreenTouchCancel);
+        this.listenFor(this.controls.pictureInPictureButton, 'touchstart', this.handlePictureInPictureTouchStart);
+        this.listenFor(this.controls.pictureInPictureButton, 'touchend', this.handlePictureInPictureTouchEnd);
+        this.listenFor(this.controls.pictureInPictureButton, 'touchcancel', this.handlePictureInPictureTouchCancel);
+        this.listenFor(this.controls.timeline, 'touchstart', this.handleTimelineTouchStart);
         this.stopListeningFor(this.controls.playButton, 'click', this.handlePlayButtonClicked);
+
+        this.controls.timeline.style.backgroundImage = '-webkit-canvas(' + this.timelineContextName + ')';
     },
 
     setControlsType: function(type) {
@@ -184,6 +147,7 @@ ControllerIOS.prototype = {
 
     addStartPlaybackControls: function() {
         this.base.appendChild(this.controls.startPlaybackButton);
+        this.showShowControlsButton(false);
     },
 
     removeStartPlaybackControls: function() {
@@ -191,9 +155,18 @@ ControllerIOS.prototype = {
             this.controls.startPlaybackButton.parentNode.removeChild(this.controls.startPlaybackButton);
     },
 
-    configureInlineControls: function() {
-        this.base.appendChild(this.controls.wirelessPlaybackStatus);
+    reconnectControls: function()
+    {
+        Controller.prototype.reconnectControls.call(this);
+
+        if (this.controlsType === ControllerIOS.StartPlaybackControls)
+            this.addStartPlaybackControls();
+    },
 
+    configureInlineControls: function() {
+        this.controls.inlinePlaybackPlaceholder.appendChild(this.controls.inlinePlaybackPlaceholderText);
+        this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextTop);
+        this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextBottom);
         this.controls.panel.appendChild(this.controls.playButton);
         this.controls.panel.appendChild(this.controls.statusDisplay);
         this.controls.panel.appendChild(this.controls.timelineBox);
@@ -203,49 +176,93 @@ ControllerIOS.prototype = {
             this.controls.timelineBox.appendChild(this.controls.timeline);
             this.controls.timelineBox.appendChild(this.controls.remainingTime);
         }
-        if (!this.isAudio())
+        if (this.isAudio()) {
+            // Hide the scrubber on audio until the user starts playing.
+            this.controls.timelineBox.classList.add(this.ClassNames.hidden);
+        } else {
+            this.updatePictureInPictureButton();
             this.controls.panel.appendChild(this.controls.fullscreenButton);
+        }
     },
 
     configureFullScreenControls: function() {
-        // Do nothing
+        // Explicitly do nothing to override base-class behavior.
+    },
+
+    controlsAreHidden: function()
+    {
+        // Controls are only ever actually hidden when they are removed from the tree
+        return !this.controls.panelContainer.parentElement;
+    },
+
+    addControls: function() {
+        this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
+        this.base.appendChild(this.controls.panelContainer);
+        this.controls.panelContainer.appendChild(this.controls.panelBackground);
+        this.controls.panelContainer.appendChild(this.controls.panel);
+        this.setNeedsTimelineMetricsUpdate();
     },
 
     updateControls: function() {
         if (this.shouldHaveStartPlaybackButton())
             this.setControlsType(ControllerIOS.StartPlaybackControls);
-        else if (this.isFullScreen())
+        else if (this.presentationMode() === "fullscreen")
             this.setControlsType(Controller.FullScreenControls);
         else
             this.setControlsType(Controller.InlineControls);
 
+        this.updateLayoutForDisplayedWidth();
         this.setNeedsTimelineMetricsUpdate();
     },
 
-    updateTime: function() {
-        Controller.prototype.updateTime.call(this);
-        this.updateProgress();
-    },
-
-    progressFillStyle: function() {
-        return 'rgba(0, 0, 0, 0.5)';
-    },
-
-    updateProgress: function() {
-        Controller.prototype.updateProgress.call(this);
+    drawTimelineBackground: function() {
+        var width = this.timelineWidth * window.devicePixelRatio;
+        var height = this.timelineHeight * window.devicePixelRatio;
 
-        var width = this.timelineWidth;
-        var height = this.timelineHeight;
-
-        // Magic number, matching the value for ::-webkit-media-controls-timeline::-webkit-slider-thumb
-        // in mediaControlsiOS.css. Since we cannot ask the thumb for its offsetWidth as it's in its own
-        // shadow dom, just hard-code the value.
-        var thumbWidth = 16;
-        var endX = thumbWidth / 2 + (width - thumbWidth) * this.video.currentTime / this.video.duration;
+        if (!width || !height)
+            return;
 
-        var context = document.getCSSCanvasContext('2d', 'timeline-' + this.timelineID, width, height);
-        context.fillStyle = 'white';
-        context.fillRect(0, 0, endX, height);
+        var played = this.video.currentTime / this.video.duration;
+        var buffered = 0;
+        var bufferedRanges = this.video.buffered;
+        if (bufferedRanges && bufferedRanges.length)
+            buffered = Math.max(bufferedRanges.end(bufferedRanges.length - 1), buffered);
+
+        buffered /= this.video.duration;
+        buffered = Math.max(buffered, played);
+
+        var ctx = document.getCSSCanvasContext('2d', this.timelineContextName, width, height);
+
+        ctx.clearRect(0, 0, width, height);
+
+        var midY = height / 2;
+
+        // 1. Draw the buffered part and played parts, using
+        // solid rectangles that are clipped to the outside of
+        // the lozenge.
+        ctx.save();
+        ctx.beginPath();
+        this.addRoundedRect(ctx, 1, midY - 3, width - 2, 6, 3);
+        ctx.closePath();
+        ctx.clip();
+        ctx.fillStyle = "white";
+        ctx.fillRect(0, 0, Math.round(width * played) + 2, height);
+        ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
+        ctx.fillRect(Math.round(width * played) + 2, 0, Math.round(width * (buffered - played)) + 2, height);
+        ctx.restore();
+
+        // 2. Draw the outline with a clip path that subtracts the
+        // middle of a lozenge. This produces a better result than
+        // stroking.
+        ctx.save();
+        ctx.beginPath();
+        this.addRoundedRect(ctx, 1, midY - 3, width - 2, 6, 3);
+        this.addRoundedRect(ctx, 2, midY - 2, width - 4, 4, 2);
+        ctx.closePath();
+        ctx.clip("evenodd");
+        ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
+        ctx.fillRect(Math.round(width * buffered) + 2, 0, width, height);
+        ctx.restore();
     },
 
     formatTime: function(time) {
@@ -258,14 +275,9 @@ ControllerIOS.prototype = {
         var sign = time < 0 ? '-' : String();
 
         if (intHours > 0)
-            return sign + intHours + ':' + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2);
+            return sign + intHours + ':' + String('0' + intMinutes).slice(-2) + ":" + String('0' + intSeconds).slice(-2);
 
-        return sign + String('00' + intMinutes).slice(intMinutes >= 10 ? -2 : -1) + ":" + String('00' + intSeconds).slice(-2);
-    },
-
-    handleTimelineChange: function(event) {
-        Controller.prototype.handleTimelineChange.call(this);
-        this.updateProgress();
+        return sign + String('0' + intMinutes).slice(intMinutes >= 10 ? -2 : -1) + ":" + String('0' + intSeconds).slice(-2);
     },
 
     handlePlayButtonTouchStart: function() {
@@ -275,9 +287,10 @@ ControllerIOS.prototype = {
     handlePlayButtonTouchEnd: function(event) {
         this.controls.playButton.classList.remove('active');
 
-        if (this.canPlay())
+        if (this.canPlay()) {
             this.video.play();
-        else
+            this.showControls();
+        } else
             this.video.pause();
 
         return true;
@@ -301,6 +314,10 @@ ControllerIOS.prototype = {
         if (!this.video.controls || this.isAudio() || this.isFullScreen() || this.gestureStartTime === undefined || this.controlsType == ControllerIOS.StartPlaybackControls)
             return;
 
+        var scaleDetectionThreshold = 0.2;
+        if (event.scale > 1 + scaleDetectionThreshold || event.scale < 1 - scaleDetectionThreshold)
+            delete this.lastDoubleTouchTime;
+
         if (this.mostRecentNumberOfTargettedTouches == 2 && event.scale >= 1.0)
             event.preventDefault();
 
@@ -311,7 +328,9 @@ ControllerIOS.prototype = {
 
         var velocity = Math.abs(event.scale - 1) / duration;
 
-        if (event.scale < 1.25 || velocity < 2)
+        var pinchOutVelocityThreshold = 2;
+        var pinchOutGestureScaleThreshold = 1.25;
+        if (velocity < pinchOutVelocityThreshold || event.scale < pinchOutGestureScaleThreshold)
             return;
 
         delete this.gestureStartTime;
@@ -323,16 +342,14 @@ ControllerIOS.prototype = {
     },
 
     handleWrapperTouchStart: function(event) {
-        if (event.target != this.base && event.target != this.controls.wirelessPlaybackStatus)
+        if (event.target != this.base && event.target != this.controls.inlinePlaybackPlaceholder)
             return;
 
         this.mostRecentNumberOfTargettedTouches = event.targetTouches.length;
 
-        if (this.controlsAreHidden()) {
+        if (this.controlsAreHidden() || !this.controls.panel.classList.contains(this.ClassNames.show)) {
             this.showControls();
-            if (this.hideTimer)
-                clearTimeout(this.hideTimer);
-            this.hideTimer = setTimeout(this.hideControls.bind(this), this.HideControlsDelay);
+            this.resetHideControlsTimer();
         } else if (!this.canPlay())
             this.hideControls();
     },
@@ -349,12 +366,29 @@ ControllerIOS.prototype = {
         this.video.style.removeProperty('-webkit-user-select');
     },
 
-    isFullScreen: function()
+    handleVisibilityChange: function(event) {
+        this.updateShouldListenForPlaybackTargetAvailabilityEvent();
+    },
+
+    handlePanelTransitionEnd: function(event)
     {
-        return this.video.webkitDisplayingFullscreen;
+        var opacity = window.getComputedStyle(this.controls.panel).opacity;
+        if (!parseInt(opacity) && !this.controlsAlwaysVisible()) {
+            this.base.removeChild(this.controls.inlinePlaybackPlaceholder);
+            this.base.removeChild(this.controls.panelContainer);
+        }
     },
 
     handleFullscreenButtonClicked: function(event) {
+        if ('webkitSetPresentationMode' in this.video) {
+            if (this.presentationMode() === 'fullscreen')
+                this.video.webkitSetPresentationMode('inline');
+            else
+                this.video.webkitSetPresentationMode('fullscreen');
+
+            return;
+        }
+
         if (this.isFullScreen())
             this.video.webkitExitFullscreen();
         else
@@ -378,43 +412,57 @@ ControllerIOS.prototype = {
         return true;
     },
 
+    handlePictureInPictureTouchStart: function() {
+        this.controls.pictureInPictureButton.classList.add('active');
+    },
+
+    handlePictureInPictureTouchEnd: function(event) {
+        this.controls.pictureInPictureButton.classList.remove('active');
+
+        this.handlePictureInPictureButtonClicked();
+
+        return true;
+    },
+
+    handlePictureInPictureTouchCancel: function(event) {
+        this.controls.pictureInPictureButton.classList.remove('active');
+        return true;
+    },
+
     handleStartPlaybackButtonTouchStart: function(event) {
-        this.controls.fullscreenButton.classList.add('active');
+        this.controls.startPlaybackButton.classList.add('active');
+        this.controls.startPlaybackButton.querySelector('.webkit-media-controls-start-playback-glyph').classList.add('active');
     },
 
     handleStartPlaybackButtonTouchEnd: function(event) {
-        this.controls.fullscreenButton.classList.remove('active');
+        this.controls.startPlaybackButton.classList.remove('active');
+        this.controls.startPlaybackButton.querySelector('.webkit-media-controls-start-playback-glyph').classList.remove('active');
+
         if (this.video.error)
             return true;
 
         this.video.play();
+        this.canToggleShowControlsButton = true;
+        this.updateControls();
 
         return true;
     },
 
     handleStartPlaybackButtonTouchCancel: function(event) {
-        this.controls.fullscreenButton.classList.remove('active');
+        this.controls.startPlaybackButton.classList.remove('active');
         return true;
     },
 
-    handleReadyStateChange: function(event) {
-        Controller.prototype.handleReadyStateChange.call(this, event);
-        this.updateControls();
+    handleTimelineTouchStart: function(event) {
+        this.scrubbing = true;
+        this.listenFor(this.controls.timeline, 'touchend', this.handleTimelineTouchEnd);
+        this.listenFor(this.controls.timeline, 'touchcancel', this.handleTimelineTouchEnd);
     },
 
-    handleWirelessPlaybackChange: function(event) {
-        this.updateWirelessPlaybackStatus();
-        this.setNeedsTimelineMetricsUpdate();
-    },
-
-    handleWirelessTargetAvailableChange: function(event) {
-        var wirelessPlaybackTargetsAvailable = event.availability == "available";
-        if (this.hasWirelessPlaybackTargets === wirelessPlaybackTargetsAvailable)
-            return;
-
-        this.hasWirelessPlaybackTargets = wirelessPlaybackTargetsAvailable;
-        this.updateWirelessTargetAvailable();
-        this.setNeedsTimelineMetricsUpdate();
+    handleTimelineTouchEnd: function(event) {
+        this.stopListeningFor(this.controls.timeline, 'touchend', this.handleTimelineTouchEnd);
+        this.stopListeningFor(this.controls.timeline, 'touchcancel', this.handleTimelineTouchEnd);
+        this.scrubbing = false;
     },
 
     handleWirelessPickerButtonTouchStart: function() {
@@ -424,8 +472,7 @@ ControllerIOS.prototype = {
 
     handleWirelessPickerButtonTouchEnd: function(event) {
         this.controls.wirelessTargetPicker.classList.remove('active');
-        this.video.webkitShowPlaybackTargetPicker();
-        return true;
+        return this.handleWirelessPickerButtonClicked();
     },
 
     handleWirelessPickerButtonTouchCancel: function(event) {
@@ -433,35 +480,148 @@ ControllerIOS.prototype = {
         return true;
     },
 
+    updateShouldListenForPlaybackTargetAvailabilityEvent: function() {
+        if (this.controlsType === ControllerIOS.StartPlaybackControls) {
+            this.setShouldListenForPlaybackTargetAvailabilityEvent(false);
+            return;
+        }
+
+        Controller.prototype.updateShouldListenForPlaybackTargetAvailabilityEvent.call(this);
+    },
+
+    updateWirelessTargetPickerButton: function() {
+    },
+
     updateStatusDisplay: function(event)
     {
-        this.controls.playButton.classList.toggle(this.ClassNames.failed, this.video.error !== null);
         this.controls.startPlaybackButton.classList.toggle(this.ClassNames.failed, this.video.error !== null);
+        this.controls.startPlaybackButton.querySelector(".webkit-media-controls-start-playback-glyph").classList.toggle(this.ClassNames.failed, this.video.error !== null);
         Controller.prototype.updateStatusDisplay.call(this, event);
     },
 
-    get pageScaleFactor() {
+    setPlaying: function(isPlaying)
+    {
+        Controller.prototype.setPlaying.call(this, isPlaying);
+
+        this.updateControls();
+
+        if (isPlaying && this.isAudio())
+            this.controls.timelineBox.classList.remove(this.ClassNames.hidden);
+
+        if (isPlaying)
+            this.hasPlayed = true;
+        else
+            this.showControls();
+    },
+
+    showControls: function()
+    {
+        this.updateShouldListenForPlaybackTargetAvailabilityEvent();
+        if (!this.video.controls)
+            return;
+
+        this.updateForShowingControls();
+        if (this.shouldHaveControls() && !this.controls.panelContainer.parentElement) {
+            this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
+            this.base.appendChild(this.controls.panelContainer);
+            this.showShowControlsButton(false);
+        }
+    },
+
+    setShouldListenForPlaybackTargetAvailabilityEvent: function(shouldListen)
+    {
+        if (shouldListen && (this.shouldHaveStartPlaybackButton() || this.video.error))
+            return;
+
+        Controller.prototype.setShouldListenForPlaybackTargetAvailabilityEvent.call(this, shouldListen);
+    },
+
+    shouldReturnVideoLayerToInline: function()
+    {
+        return this.presentationMode() === 'inline';
+    },
+
+    updatePictureInPicturePlaceholder: function(event)
+    {
+        var presentationMode = this.presentationMode();
+
+        switch (presentationMode) {
+            case 'inline':
+                this.controls.panelContainer.classList.remove(this.ClassNames.pictureInPicture);
+                break;
+            case 'picture-in-picture':
+                this.controls.panelContainer.classList.add(this.ClassNames.pictureInPicture);
+                break;
+            default:
+                this.controls.panelContainer.classList.remove(this.ClassNames.pictureInPicture);
+                break;
+        }
+
+        Controller.prototype.updatePictureInPicturePlaceholder.call(this, event);
+    },
+
+    // Due to the bad way we are faking inheritance here, in particular the extends method
+    // on Controller.prototype, we don't copy getters and setters from the prototype. This
+    // means we have to implement them again, here in the subclass.
+    // FIXME: Use ES6 classes!
+
+    get scrubbing()
+    {
+        return Object.getOwnPropertyDescriptor(Controller.prototype, "scrubbing").get.call(this);
+    },
+
+    set scrubbing(flag)
+    {
+        Object.getOwnPropertyDescriptor(Controller.prototype, "scrubbing").set.call(this, flag);
+    },
+
+    get pageScaleFactor()
+    {
         return this._pageScaleFactor;
     },
 
-    set pageScaleFactor(newScaleFactor) {
-        if (this._pageScaleFactor === newScaleFactor)
+    set pageScaleFactor(newScaleFactor)
+    {
+        if (!newScaleFactor || this._pageScaleFactor === newScaleFactor)
             return;
 
         this._pageScaleFactor = newScaleFactor;
 
-        if (newScaleFactor) {
-            var scaleTransform = "scale(" + (1 / newScaleFactor) + ")";
-            if (this.controls.startPlaybackButton)
-                this.controls.startPlaybackButton.style.webkitTransform = scaleTransform;
-            if (this.controls.panel) {
-                this.controls.panel.style.width = (newScaleFactor * 100) + "%";
-                this.controls.panel.style.webkitTransform = scaleTransform;
-                this.setNeedsTimelineMetricsUpdate();
-                this.updateProgress();
+        var scaleValue = 1 / newScaleFactor;
+        var scaleTransform = "scale(" + scaleValue + ")";
+
+        function applyScaleFactorToElement(element) {
+            if (scaleValue > 1) {
+                element.style.zoom = scaleValue;
+                element.style.webkitTransform = "scale(1)";
+            } else {
+                element.style.zoom = 1;
+                element.style.webkitTransform = scaleTransform;
+            }
+        }
+
+        if (this.controls.startPlaybackButton)
+            applyScaleFactorToElement(this.controls.startPlaybackButton);
+        if (this.controls.panel) {
+            applyScaleFactorToElement(this.controls.panel);
+            if (scaleValue > 1) {
+                this.controls.panel.style.width = "100%";
+                this.controls.timelineBox.style.webkitTextSizeAdjust = (100 * scaleValue) + "%";
+            } else {
+                var bottomAligment = -2 * scaleValue;
+                this.controls.panel.style.bottom = bottomAligment + "px";
+                this.controls.panel.style.paddingBottom = -(newScaleFactor * bottomAligment) + "px";
+                this.controls.panel.style.width = Math.round(newScaleFactor * 100) + "%";
+                this.controls.timelineBox.style.webkitTextSizeAdjust = "auto";
             }
+            this.controls.panelBackground.style.height = (50 * scaleValue) + "px";
+
+            this.setNeedsTimelineMetricsUpdate();
+            this.updateProgress();
+            this.scheduleUpdateLayoutForDisplayedWidth();
         }
-    }
+    },
+
 };
 
 Object.create(Controller.prototype).extend(ControllerIOS.prototype);