/* * Copyright (C) 2017 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const InsideMargin = 6; // Minimum margin to guarantee around all controls, this constant needs to stay in sync with the --inline-controls-inside-margin CSS variable. const BottomControlsBarHeight = 31; // This constant needs to stay in sync with the --inline-controls-bar-height CSS variable. class InlineMediaControls extends MediaControls { constructor(options) { super(options); this.element.classList.add("inline"); this.skipBackButton = new SkipBackButton(this); this.skipForwardButton = new SkipForwardButton(this); this.topLeftControlsBar = new ControlsBar("top-left"); this._topLeftControlsBarContainer = this.topLeftControlsBar.addChild(new ButtonsContainer); this.topRightControlsBar = new ControlsBar("top-right"); this._topRightControlsBarContainer = this.topRightControlsBar.addChild(new ButtonsContainer); this.leftContainer = new ButtonsContainer({ cssClassName: "left" }); this.rightContainer = new ButtonsContainer({ cssClassName: "right" }); this._shouldUseAudioLayout = false; this._shouldUseSingleBarLayout = false; this.showsStartButton = false; this._updateBottomControlsBarLabel(); } // Public set shouldUseAudioLayout(flag) { if (this._shouldUseAudioLayout === flag) return; this._shouldUseAudioLayout = flag; this.element.classList.toggle("audio", flag); this.needsLayout = true; this._updateBottomControlsBarLabel(); } set shouldUseSingleBarLayout(flag) { if (this._shouldUseSingleBarLayout === flag) return; this._shouldUseSingleBarLayout = flag; this.needsLayout = true; } get showsStartButton() { return !!this._showsStartButton; } set showsStartButton(flag) { if (this._showsStartButton === flag) return; this._showsStartButton = flag; this.layout(); } // Protected layout() { super.layout(); const children = []; if (this.placard) { children.push(this.placard); if (this.placardPreventsControlsBarDisplay()) { this.children = children; return; } } if (!this.visible) { this.children = children; return; } // The controls might be too small to allow showing anything at all. if (!this._shouldUseAudioLayout && (this.width < MinimumSizeToShowAnyControl || this.height < MinimumSizeToShowAnyControl)) { this.children = children; return; } // If we should show the start button, then only show that button. if (this._showsStartButton) { this.playPauseButton.style = this.width <= MaximumSizeToShowSmallProminentControl || this.height <= MaximumSizeToShowSmallProminentControl ? Button.Styles.SmallCenter : Button.Styles.Center; this.children = [this.playPauseButton]; return; } if (!this.bottomControlsBar) return; // Ensure the tracks panel is a child if it were presented. if (this.tracksPanel.presented) children.push(this.tracksPanel); // Update the top left controls bar. this._topLeftControlsBarContainer.children = this._topLeftContainerButtons(); this._topLeftControlsBarContainer.layout(); this.topLeftControlsBar.width = this._topLeftControlsBarContainer.width; this.topLeftControlsBar.visible = this._topLeftControlsBarContainer.children.some(button => button.visible); // Compute the visible size for the controls bar. this.bottomControlsBar.width = this._shouldUseAudioLayout ? this.width : (this.width - 2 * InsideMargin); // Compute the absolute minimum width to display the center control (status label or time control). const centerControl = this.statusLabel.enabled ? this.statusLabel : this.timeControl; let minimumCenterControlWidth = centerControl.minimumWidth; // Worst case scenario is that we can't fit the center control with the required margins. In this case, // we need to make the play/pause button display as a corner button. const minimumControlsBarWidthForCenterControl = minimumCenterControlWidth + this.leftContainer.leftMargin + this.rightContainer.rightMargin; if (this.bottomControlsBar.width < minimumControlsBarWidthForCenterControl) { this.playPauseButton.style = Button.Styles.Corner; if (!this._shouldUseSingleBarLayout && this.height >= 82) { children.push(this.topLeftControlsBar); this._addTopRightBarWithMuteButtonToChildren(children); } this.children = children.concat(this.playPauseButton); return; } // Now allow the minimum center element to display with fewer constraints. minimumCenterControlWidth = centerControl.idealMinimumWidth; // Iterate through controls to see if we need to drop any of them. Reset all default states before we proceed. this.bottomControlsBar.visible = true; this.playPauseButton.style = Button.Styles.Bar; this.leftContainer.children = this._leftContainerButtons(); this.rightContainer.children = this._rightContainerButtons(); this.rightContainer.children.concat(this.leftContainer.children).forEach(button => delete button.dropped); this.muteButton.style = this.preferredMuteButtonStyle; this.muteButton.usesRTLIconVariant = false; for (let button of this._droppableButtons()) { // If the button is not enabled, we can skip it. if (!button.enabled) continue; // Ensure button containers are laid out with latest constraints. this.leftContainer.layout(); this.rightContainer.layout(); // Nothing left to do if the combined width of both containers and the time control is shorter than the available width. if (this.leftContainer.width + minimumCenterControlWidth + this.rightContainer.width < this.bottomControlsBar.width) break; // This button must now be dropped. button.dropped = true; } // Update layouts once more. this.leftContainer.layout(); this.rightContainer.layout(); const widthLeftOfTimeControl = this.leftContainer.children.length > 0 ? this.leftContainer.width : this.leftContainer.leftMargin; const widthRightOfTimeControl = this.rightContainer.children.length > 0 ? this.rightContainer.width : this.rightContainer.rightMargin; centerControl.x = widthLeftOfTimeControl; centerControl.width = this.bottomControlsBar.width - widthLeftOfTimeControl - widthRightOfTimeControl; centerControl.layout(); // Add visible children. const controlsBarChildren = []; if (this.leftContainer.children.length) controlsBarChildren.push(this.leftContainer); controlsBarChildren.push(centerControl); if (this.rightContainer.children.length) { controlsBarChildren.push(this.rightContainer); this.rightContainer.x = this.bottomControlsBar.width - this.rightContainer.width; } // Ensure we position the bottom controls bar at the bottom of the frame, accounting for // the inside margin, unless this would yield a position outside of the frame. this.bottomControlsBar.y = Math.max(0, this.height - BottomControlsBarHeight - InsideMargin); this.bottomControlsBar.children = controlsBarChildren; if (!this._shouldUseAudioLayout && !this._shouldUseSingleBarLayout) children.push(this.topLeftControlsBar); children.push(this.bottomControlsBar); if (this.muteButton.style === Button.Styles.Corner || (this.muteButton.dropped && !this._shouldUseAudioLayout && !this._shouldUseSingleBarLayout)) this._addTopRightBarWithMuteButtonToChildren(children); this.children = children; } commitProperty(propertyName) { // We override the default behavior of the "visible" property, which usually means the node // will not be displayed if false, but we want to allow placards to be visible, even when // controls are supposed to be hidden. if (propertyName !== "visible") super.commitProperty(propertyName); } get preferredMuteButtonStyle() { return (this._shouldUseAudioLayout || this._shouldUseSingleBarLayout) ? Button.Styles.Bar : Button.Styles.Corner; } // Private _updateBottomControlsBarLabel() { this.bottomControlsBar.element.setAttribute("aria-label", this._shouldUseAudioLayout ? UIString("Audio Controls") : UIString("Video Controls")); } _topLeftContainerButtons() { if (this._shouldUseSingleBarLayout) return []; if (this.usesLTRUserInterfaceLayoutDirection) return [this.fullscreenButton, this.pipButton]; return [this.pipButton, this.fullscreenButton]; } _leftContainerButtons() { return [this.skipBackButton, this.playPauseButton, this.skipForwardButton]; } _rightContainerButtons() { if (this._shouldUseAudioLayout) return [this.muteButton, this.airplayButton]; if (this._shouldUseSingleBarLayout) return [this.muteButton, this.airplayButton, this.pipButton, this.tracksButton, this.fullscreenButton]; const buttons = []; if (this.preferredMuteButtonStyle === Button.Styles.Bar) buttons.push(this.muteButton); buttons.push(this.airplayButton, this.tracksButton); return buttons; } _droppableButtons() { if (this._shouldUseSingleBarLayout) return [this.skipForwardButton, this.skipBackButton, this.airplayButton, this.tracksButton, this.pipButton, this.fullscreenButton, this.muteButton]; const buttons = [this.skipForwardButton, this.skipBackButton, this.airplayButton, this.tracksButton]; if (this.preferredMuteButtonStyle === Button.Styles.Bar) buttons.push(this.muteButton); return buttons; } _addTopRightBarWithMuteButtonToChildren(children) { if (!this.muteButton.enabled) return; delete this.muteButton.dropped; this.muteButton.style = Button.Styles.Bar; this.muteButton.usesRTLIconVariant = !this.usesLTRUserInterfaceLayoutDirection; this._topRightControlsBarContainer.children = [this.muteButton]; this._topRightControlsBarContainer.layout(); this.topRightControlsBar.width = this._topRightControlsBarContainer.width; children.push(this.topRightControlsBar); } }