From 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Tue, 27 Jun 2017 06:07:23 +0000 Subject: webkitgtk-2.16.5 --- .../Modules/mediacontrols/MediaControlsHost.cpp | 294 +++ .../Modules/mediacontrols/MediaControlsHost.h | 101 + .../Modules/mediacontrols/MediaControlsHost.idl | 65 + .../Modules/mediacontrols/assets-apple-iOS.svg | 52 + .../Modules/mediacontrols/mediaControlsApple.css | 1153 +++++++++ .../Modules/mediacontrols/mediaControlsApple.js | 2509 ++++++++++++++++++++ .../Modules/mediacontrols/mediaControlsBase.css | 758 ++++++ .../Modules/mediacontrols/mediaControlsBase.js | 1336 +++++++++++ .../Modules/mediacontrols/mediaControlsGtk.js | 258 ++ .../Modules/mediacontrols/mediaControlsiOS.css | 733 ++++++ .../Modules/mediacontrols/mediaControlsiOS.js | 628 +++++ 11 files changed, 7887 insertions(+) create mode 100644 Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp create mode 100644 Source/WebCore/Modules/mediacontrols/MediaControlsHost.h create mode 100644 Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl create mode 100644 Source/WebCore/Modules/mediacontrols/assets-apple-iOS.svg create mode 100644 Source/WebCore/Modules/mediacontrols/mediaControlsApple.css create mode 100644 Source/WebCore/Modules/mediacontrols/mediaControlsApple.js create mode 100644 Source/WebCore/Modules/mediacontrols/mediaControlsBase.css create mode 100644 Source/WebCore/Modules/mediacontrols/mediaControlsBase.js create mode 100644 Source/WebCore/Modules/mediacontrols/mediaControlsGtk.js create mode 100644 Source/WebCore/Modules/mediacontrols/mediaControlsiOS.css create mode 100644 Source/WebCore/Modules/mediacontrols/mediaControlsiOS.js (limited to 'Source/WebCore/Modules/mediacontrols') diff --git a/Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp b/Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp new file mode 100644 index 000000000..8d8eeff0c --- /dev/null +++ b/Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2013, 2014 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. + */ + +#include "config.h" + +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + +#include "MediaControlsHost.h" + +#include "CaptionUserPreferences.h" +#include "Element.h" +#include "HTMLMediaElement.h" +#include "Logging.h" +#include "MediaControlElements.h" +#include "Page.h" +#include "PageGroup.h" +#include "RenderTheme.h" +#include "TextTrack.h" +#include "TextTrackList.h" +#include "UUID.h" +#include + +namespace WebCore { + +const AtomicString& MediaControlsHost::automaticKeyword() +{ + static NeverDestroyed automatic("automatic", AtomicString::ConstructFromLiteral); + return automatic; +} + +const AtomicString& MediaControlsHost::forcedOnlyKeyword() +{ + static NeverDestroyed forcedOn("forced-only", AtomicString::ConstructFromLiteral); + return forcedOn; +} + +const AtomicString& MediaControlsHost::alwaysOnKeyword() +{ + static NeverDestroyed alwaysOn("always-on", AtomicString::ConstructFromLiteral); + return alwaysOn; +} + +const AtomicString& MediaControlsHost::manualKeyword() +{ + static NeverDestroyed alwaysOn("manual", AtomicString::ConstructFromLiteral); + return alwaysOn; +} + + +Ref MediaControlsHost::create(HTMLMediaElement* mediaElement) +{ + return adoptRef(*new MediaControlsHost(mediaElement)); +} + +MediaControlsHost::MediaControlsHost(HTMLMediaElement* mediaElement) + : m_mediaElement(mediaElement) +{ + ASSERT(mediaElement); +} + +MediaControlsHost::~MediaControlsHost() +{ +} + +Vector> MediaControlsHost::sortedTrackListForMenu(TextTrackList& trackList) +{ + Page* page = m_mediaElement->document().page(); + if (!page) + return { }; + + return page->group().captionPreferences().sortedTrackListForMenu(&trackList); +} + +Vector> MediaControlsHost::sortedTrackListForMenu(AudioTrackList& trackList) +{ + Page* page = m_mediaElement->document().page(); + if (!page) + return { }; + + return page->group().captionPreferences().sortedTrackListForMenu(&trackList); +} + +String MediaControlsHost::displayNameForTrack(const std::optional& track) +{ + if (!track) + return emptyString(); + + Page* page = m_mediaElement->document().page(); + if (!page) + return emptyString(); + + return WTF::visit([&page](auto& track) { + return page->group().captionPreferences().displayNameForTrack(track.get()); + }, track.value()); +} + +TextTrack* MediaControlsHost::captionMenuOffItem() +{ + return TextTrack::captionMenuOffItem(); +} + +TextTrack* MediaControlsHost::captionMenuAutomaticItem() +{ + return TextTrack::captionMenuAutomaticItem(); +} + +AtomicString MediaControlsHost::captionDisplayMode() const +{ + Page* page = m_mediaElement->document().page(); + if (!page) + return emptyAtom; + + switch (page->group().captionPreferences().captionDisplayMode()) { + case CaptionUserPreferences::Automatic: + return automaticKeyword(); + case CaptionUserPreferences::ForcedOnly: + return forcedOnlyKeyword(); + case CaptionUserPreferences::AlwaysOn: + return alwaysOnKeyword(); + case CaptionUserPreferences::Manual: + return manualKeyword(); + default: + ASSERT_NOT_REACHED(); + return emptyAtom; + } +} + +void MediaControlsHost::setSelectedTextTrack(TextTrack* track) +{ + m_mediaElement->setSelectedTextTrack(track); +} + +Element* MediaControlsHost::textTrackContainer() +{ + if (!m_textTrackContainer) { + m_textTrackContainer = MediaControlTextTrackContainerElement::create(m_mediaElement->document()); + m_textTrackContainer->setMediaController(m_mediaElement); + } + return m_textTrackContainer.get(); +} + +void MediaControlsHost::updateTextTrackContainer() +{ + if (m_textTrackContainer) + m_textTrackContainer->updateDisplay(); +} + +void MediaControlsHost::enteredFullscreen() +{ + if (m_textTrackContainer) + m_textTrackContainer->enteredFullscreen(); +} + +void MediaControlsHost::exitedFullscreen() +{ + if (m_textTrackContainer) + m_textTrackContainer->exitedFullscreen(); +} + +void MediaControlsHost::updateCaptionDisplaySizes() +{ + if (m_textTrackContainer) + m_textTrackContainer->updateSizes(true); +} + +bool MediaControlsHost::allowsInlineMediaPlayback() const +{ + return !m_mediaElement->mediaSession().requiresFullscreenForVideoPlayback(*m_mediaElement); +} + +bool MediaControlsHost::supportsFullscreen() const +{ + return m_mediaElement->supportsFullscreen(HTMLMediaElementEnums::VideoFullscreenModeStandard); +} + +bool MediaControlsHost::isVideoLayerInline() const +{ + return m_mediaElement->isVideoLayerInline(); +} + +bool MediaControlsHost::isInMediaDocument() const +{ + return m_mediaElement->document().isMediaDocument(); +} + +void MediaControlsHost::setPreparedToReturnVideoLayerToInline(bool value) +{ + m_mediaElement->setPreparedToReturnVideoLayerToInline(value); +} + +bool MediaControlsHost::userGestureRequired() const +{ + return !m_mediaElement->mediaSession().playbackPermitted(*m_mediaElement); +} + +String MediaControlsHost::externalDeviceDisplayName() const +{ +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + MediaPlayer* player = m_mediaElement->player(); + if (!player) { + LOG(Media, "MediaControlsHost::externalDeviceDisplayName - returning \"\" because player is NULL"); + return emptyString(); + } + + String name = player->wirelessPlaybackTargetName(); + LOG(Media, "MediaControlsHost::externalDeviceDisplayName - returning \"%s\"", name.utf8().data()); + + return name; +#else + return emptyString(); +#endif +} + +auto MediaControlsHost::externalDeviceType() const -> DeviceType +{ +#if !ENABLE(WIRELESS_PLAYBACK_TARGET) + return DeviceType::None; +#else + MediaPlayer* player = m_mediaElement->player(); + if (!player) { + LOG(Media, "MediaControlsHost::externalDeviceType - returning \"none\" because player is NULL"); + return DeviceType::None; + } + + switch (player->wirelessPlaybackTargetType()) { + case MediaPlayer::TargetTypeNone: + return DeviceType::None; + case MediaPlayer::TargetTypeAirPlay: + return DeviceType::Airplay; + case MediaPlayer::TargetTypeTVOut: + return DeviceType::Tvout; + } + + ASSERT_NOT_REACHED(); + return DeviceType::None; +#endif +} + +bool MediaControlsHost::controlsDependOnPageScaleFactor() const +{ + return m_mediaElement->mediaControlsDependOnPageScaleFactor(); +} + +void MediaControlsHost::setControlsDependOnPageScaleFactor(bool value) +{ + m_mediaElement->setMediaControlsDependOnPageScaleFactor(value); +} + +String MediaControlsHost::generateUUID() const +{ + return createCanonicalUUIDString(); +} + +String MediaControlsHost::shadowRootCSSText() const +{ + Page* page = m_mediaElement->document().page(); + if (!page) + return emptyString(); + return RenderTheme::themeForPage(page)->modernMediaControlsStyleSheet(); +} + +String MediaControlsHost::base64StringForIconAndPlatform(const String& iconName, const String& platform) const +{ + Page* page = m_mediaElement->document().page(); + if (!page) + return emptyString(); + return RenderTheme::themeForPage(page)->mediaControlsBase64StringForIconAndPlatform(iconName, platform); +} + +} + +#endif diff --git a/Source/WebCore/Modules/mediacontrols/MediaControlsHost.h b/Source/WebCore/Modules/mediacontrols/MediaControlsHost.h new file mode 100644 index 000000000..1f034e522 --- /dev/null +++ b/Source/WebCore/Modules/mediacontrols/MediaControlsHost.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2013, 2014 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. + */ + +#pragma once + +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + +#include +#include +#include +#include +#include + +namespace WebCore { + +class AudioTrack; +class AudioTrackList; +class Element; +class HTMLMediaElement; +class MediaControlTextTrackContainerElement; +class TextTrack; +class TextTrackList; + +class MediaControlsHost : public RefCounted { +public: + static Ref create(HTMLMediaElement*); + ~MediaControlsHost(); + + static const AtomicString& automaticKeyword(); + static const AtomicString& forcedOnlyKeyword(); + static const AtomicString& alwaysOnKeyword(); + static const AtomicString& manualKeyword(); + + Vector> sortedTrackListForMenu(TextTrackList&); + Vector> sortedTrackListForMenu(AudioTrackList&); + + using TextOrAudioTrack = WTF::Variant, RefPtr>; + String displayNameForTrack(const std::optional&); + + TextTrack* captionMenuOffItem(); + TextTrack* captionMenuAutomaticItem(); + AtomicString captionDisplayMode() const; + void setSelectedTextTrack(TextTrack*); + Element* textTrackContainer(); + void updateTextTrackContainer(); + bool allowsInlineMediaPlayback() const; + bool supportsFullscreen() const; + bool isVideoLayerInline() const; + bool isInMediaDocument() const; + bool userGestureRequired() const; + void setPreparedToReturnVideoLayerToInline(bool); + + void updateCaptionDisplaySizes(); + void enteredFullscreen(); + void exitedFullscreen(); + + String externalDeviceDisplayName() const; + + enum class DeviceType { None, Airplay, Tvout }; + DeviceType externalDeviceType() const; + + bool controlsDependOnPageScaleFactor() const; + void setControlsDependOnPageScaleFactor(bool v); + + String generateUUID() const; + + String shadowRootCSSText() const; + String base64StringForIconAndPlatform(const String& iconName, const String& platform) const; + +private: + MediaControlsHost(HTMLMediaElement*); + + HTMLMediaElement* m_mediaElement; + RefPtr m_textTrackContainer; +}; + +} + +#endif diff --git a/Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl b/Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl new file mode 100644 index 000000000..3e4fcb670 --- /dev/null +++ b/Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013, 2014 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. + */ + +enum DeviceType { + "none", + "airplay", + "tvout" +}; + +[ + Conditional=MEDIA_CONTROLS_SCRIPT, + ImplementationLacksVTable, + NoInterfaceObject, +] interface MediaControlsHost { + sequence sortedTrackListForMenu(TextTrackList trackList); + sequence sortedTrackListForMenu(AudioTrackList trackList); + DOMString displayNameForTrack((TextTrack or AudioTrack)? track); + readonly attribute TextTrack captionMenuOffItem; + readonly attribute TextTrack captionMenuAutomaticItem; + readonly attribute DOMString captionDisplayMode; + void setSelectedTextTrack(TextTrack? track); + void setPreparedToReturnVideoLayerToInline(boolean prepared); + readonly attribute HTMLElement textTrackContainer; + readonly attribute boolean allowsInlineMediaPlayback; + readonly attribute boolean supportsFullscreen; + readonly attribute boolean isVideoLayerInline; + readonly attribute boolean userGestureRequired; + readonly attribute boolean isInMediaDocument; + + readonly attribute DOMString externalDeviceDisplayName; + readonly attribute DeviceType externalDeviceType; + + attribute boolean controlsDependOnPageScaleFactor; + + void updateTextTrackContainer(); + void enteredFullscreen(); + void exitedFullscreen(); + + DOMString generateUUID(); + + [EnabledAtRuntime=ModernMediaControls] readonly attribute DOMString shadowRootCSSText; + [EnabledAtRuntime=ModernMediaControls] DOMString base64StringForIconAndPlatform(DOMString iconName, DOMString platform); +}; diff --git a/Source/WebCore/Modules/mediacontrols/assets-apple-iOS.svg b/Source/WebCore/Modules/mediacontrols/assets-apple-iOS.svg new file mode 100644 index 000000000..db2a2d334 --- /dev/null +++ b/Source/WebCore/Modules/mediacontrols/assets-apple-iOS.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/WebCore/Modules/mediacontrols/mediaControlsApple.css b/Source/WebCore/Modules/mediacontrols/mediaControlsApple.css new file mode 100644 index 000000000..b7884d285 --- /dev/null +++ b/Source/WebCore/Modules/mediacontrols/mediaControlsApple.css @@ -0,0 +1,1153 @@ +/* + * Copyright (C) 2013, 2014, 2015 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, + * 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. + */ + +audio { + width: 200px; + height: 25px; +} + +body:-webkit-full-page-media { + background-color: rgb(38, 38, 38); +} + +video:-webkit-full-page-media { + margin: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +video:-webkit-full-page-media::-webkit-media-controls-panel { + bottom: 0px; +} + +video:-webkit-full-page-media::-webkit-media-controls-panel.no-video { + opacity: 1; +} + +::-webkit-media-controls { + width: inherit; + height: inherit; + position: relative; + display: -webkit-flex !important; + -webkit-align-items: stretch; + -webkit-justify-content: flex-end; + -webkit-flex-direction: column; + font: -webkit-small-control; + white-space: nowrap; + -webkit-font-smoothing: subpixel-antialiased; +} + +::-webkit-media-controls.placeholder-showing { + overflow: hidden; +} + +video::-webkit-media-text-track-container, +audio::-webkit-media-text-track-container { + position: relative; + -webkit-flex: 1 1 auto; +} + +video::-webkit-media-controls-panel, +audio::-webkit-media-controls-panel { + box-sizing: border-box; + position: relative; + bottom: 0; + width: 100%; + min-height: 25px; + height: 25px; + line-height: 25px; + -webkit-user-select: none; + -webkit-user-drag: element; + background-color: transparent; + + display: -webkit-flex; + -webkit-flex-direction: row; + -webkit-align-items: center; + -webkit-user-select: none; + + direction: ltr; + + transition: opacity 0.25s linear; + + -webkit-text-zoom: reset +} + +video::-webkit-media-controls-panel { + opacity: 0; + padding-top: 20px; + min-height: 45px; + height: 45px; +} + +video::-webkit-media-controls-panel.show, +video::-webkit-media-controls-panel.paused, +video::-webkit-media-controls-panel:hover { + cursor: inherit; + opacity: 1; +} + +video::-webkit-media-controls-panel.picture-in-picture { + pointer-events: none; +} + +audio::-webkit-media-show-controls { + display: none !important; +} +video::-webkit-media-show-controls { + position: absolute; + left: 0; + bottom: 0; + display: block; + margin: 0; + padding: 0; + width: 100%; + height: 10px; + opacity: 0; + border: 0; + background: none; + -webkit-appearance: none; +} + +video::-webkit-media-controls-panel-background-container, +audio::-webkit-media-controls-panel-background-container { + position: absolute; + left: 0; + top: 0; + width: 100%; +} + +video::-webkit-media-controls-panel-background-container { + z-index: 0; +} + +video::-webkit-media-controls-panel-background-container { + min-height: 45px; + height: 45px; + -webkit-clip-path: inset(20px 0px 0px 0px); +} + +video::-webkit-media-controls-panel-tint, +audio::-webkit-media-controls-panel-tint { + position: absolute; + left: 0; + top: 0; + width: 100%; + background-color: transparent; +} + +video::-webkit-media-controls-panel-tint { + min-height: 45px; + height: 45px; + background-color: rgb(41, 41, 41); + mix-blend-mode: lighten; +} + +video::-webkit-media-controls-panel-background, +audio::-webkit-media-controls-panel-background { + position: absolute; + left: 0; + top: 0; + width: 100%; + min-height: 25px; + height: 25px; + background-color: rgb(41, 41, 41); +} + +video::-webkit-media-controls-panel-background { + min-height: 45px; + height: 45px; + background-color: rgba(30, 30, 30, 0.45); + -webkit-backdrop-filter: saturate(180%) blur(20px); +} + +video::-webkit-media-controls-panel button, +audio::-webkit-media-controls-panel button { + -webkit-appearance: none; + display: block; + padding: 0; + border: 0; + height: 15px; + background-color: transparent; + color: white; + background-origin: content-box; + background-repeat: no-repeat; + background-position: center; +} + +audio::-webkit-media-controls-panel button { + z-index: 0; +} + +video::-webkit-media-controls-panel button { + mix-blend-mode: plus-lighter; + -webkit-transform: translateZ(0); +} + +video::-webkit-media-controls-panel button:focus, +audio::-webkit-media-controls-panel button:focus { + outline: 0; +} + +video::-webkit-media-controls-rewind-button, +audio::-webkit-media-controls-rewind-button { + background-image: url('data:image/svg+xml,'); + width: 16px; + min-width: 16px; + height: 18px; + margin-bottom: 1px; + margin-left: 8px; + margin-right: 8px; +} + +video::-webkit-media-controls-play-button, +audio::-webkit-media-controls-play-button { + background-image: url('data:image/svg+xml,'); + margin-left: 8px; + margin-right: 8px; + width: 12px; + min-width: 12px; +} + +video::-webkit-media-controls-play-button.paused, +audio::-webkit-media-controls-play-button.paused { + background-image: url('data:image/svg+xml,'); + width: 12px; +} + +video::-webkit-media-controls-panel .mute-box, +audio::-webkit-media-controls-panel .mute-box { + width: 14px; + min-width: 14px; + height: 25px; + margin-right: 8px; + margin-left: 8px; + position: relative; + display: -webkit-flex; + -webkit-flex-direction: column; + -webkit-justify-content: center; + -webkit-align-items: center; +} + +video::-webkit-media-controls-mute-button, +audio::-webkit-media-controls-mute-button, +video::-webkit-media-controls-volume-max-button { + width: 14px; + min-width: 14px; + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-panel .volume-box, +audio::-webkit-media-controls-panel .volume-box { + position: absolute; + box-sizing: border-box; + width: 63px; + bottom: 5px; + left: -25px; + + -webkit-clip-path: inset(20px 20px 20px 20px round 4px 4px 0px 0px); + background-color: transparent; + overflow: hidden; + + display: -webkit-flex; + -webkit-flex-direction: row; + -webkit-align-items: center; + -webkit-justify-content: flex-end; + + opacity: 0; + /* make zero height (rather than display:none) for AX and FKA */ + height: 0px; /* will become 116px when shown */ + +} + +audio::-webkit-media-controls-panel .volume-box { + background-color: black; +} + +video::-webkit-media-controls-volume-slider-container-background, +audio::-webkit-media-controls-volume-slider-container-background { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; + background-color: rgba(30, 30, 30, 0.45); +} + +video::-webkit-media-controls-volume-slider-container-background { + background-color: rgba(30, 30, 30, 0.45); + -webkit-backdrop-filter: saturate(180%) blur(20px); +} + +video::-webkit-media-controls-volume-slider-container-tint, +audio::-webkit-media-controls-volume-slider-container-tint { + position: absolute; + left: 0; + top: 0; + height: 100%; + width: 100%; + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; + background-color: rgb(41, 41, 41); +} + +video::-webkit-media-controls-volume-slider-container-tint { + mix-blend-mode: lighten; +} + +/* FIXME: needs CSS4 !subject selector to show when slider inside .volume-box is focused */ +video::-webkit-media-controls-panel .mute-box:hover .volume-box, +video::-webkit-media-controls-panel .volume-box:hover, +video::-webkit-media-controls-panel .volume-box:active, +audio::-webkit-media-controls-panel .mute-box:hover .volume-box, +audio::-webkit-media-controls-panel .volume-box:hover, +audio::-webkit-media-controls-panel .volume-box:active { + opacity: 1; + /* resize to usable amount (rather than display:none) for AX and FKA */ + height: 116px; +} + +audio::-webkit-media-controls-volume-slider, +video::-webkit-media-controls-volume-slider { + -webkit-appearance: none !important; + box-sizing: border-box !important; + height: 9px !important; + min-width: 64px !important; + width: 64px !important; + padding: 0 !important; + margin: 0 !important; + + background-color: transparent !important; + background-size: 100% 100%; + background-repeat: no-repeat; + -webkit-transform-origin: 0 0; + -webkit-transform: rotate(-90deg) translateY(28px) translateX(-40px); +} + +video::-webkit-media-controls-volume-slider { + mix-blend-mode: plus-lighter; +} + +video::-webkit-media-controls-volume-slider::-webkit-slider-thumb, +audio::-webkit-media-controls-volume-slider::-webkit-slider-thumb { + -webkit-appearance: none !important; + width: 7px !important; + height: 7px !important; + visibility: hidden; +} + +video::-webkit-media-controls-mute-button.muted, +audio::-webkit-media-controls-mute-button.muted, +video::-webkit-media-controls-volume-min-button { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-wireless-playback-picker-button, +audio::-webkit-media-controls-wireless-playback-picker-button { + margin-right: 8px; + margin-left: 8px; + width: 16px; + min-width: 16px; +} + +video::-webkit-media-controls-wireless-playback-picker-button.playing, +audio::-webkit-media-controls-wireless-playback-picker-button.playing { + mix-blend-mode: normal; +} + +video::-webkit-media-controls-toggle-closed-captions-button, +audio::-webkit-media-controls-toggle-closed-captions-button { + width: 16px; + min-width: 16px; + margin-right: 8px; + margin-left: 8px; + background-image: url('data:image/svg+xml, '); + outline: 0; +} + +video::-webkit-media-controls-closed-captions-container, +audio::-webkit-media-controls-closed-captions-container { + -webkit-appearance: media-closed-captions-container; + position: absolute; + display: block; + right: 38px; + bottom: 29px; + max-width: calc(100% - 48px); /* right + 10px */ + max-height: calc(100% - 39px); /* bottom + 10px */ + overflow-x: hidden; + overflow-y: scroll; + background-color: rgba(0, 0, 0, 0.8); + border: 1px solid rgba(128, 128, 128, 0.75); + border-radius: 6px; + cursor: default; + z-index: 2; + text-align: initial; +} + +video::-webkit-media-controls-closed-captions-container .list, +audio::-webkit-media-controls-closed-captions-container .list { + display: block; + -webkit-user-select: none; +} + +video::-webkit-media-controls-closed-captions-container h3, +audio::-webkit-media-controls-closed-captions-container h3 { + margin: 0; + color: rgb(140, 140, 140); + text-shadow: 0 1px 0 black; + -webkit-margin-start: 16px; + padding-top: 4px; + font-size: 11px; +} + +video::-webkit-media-controls-closed-captions-container ul, +audio::-webkit-media-controls-closed-captions-container ul { + list-style-type: none; + margin: 0 0 8px 0; + padding: 0; +} + +video::-webkit-media-controls-closed-captions-container li, +audio::-webkit-media-controls-closed-captions-container li { + position: relative; + color: white; + background-image: none; + text-shadow: 0 1px 0 black; + margin: 0; + padding-left: 28px; + padding-right: 28px; + padding-top: 0.15em; + padding-bottom: 0.2em; + box-sizing: border-box; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; +} + +video::-webkit-media-controls-closed-captions-container li:focus, +audio::-webkit-media-controls-closed-captions-container li:focus { + outline: 0; + background-color: rgba(140, 140, 140, 0.5); +} + +video::-webkit-media-controls-closed-captions-container li:hover, +audio::-webkit-media-controls-closed-captions-container li:hover { + background-color: rgba(26, 68, 243, 0.6); +} + +video::-webkit-media-controls-closed-captions-container li .checkmark-container, +audio::-webkit-media-controls-closed-captions-container li .checkmark-container { + display: none; + position: absolute; + top: 0.25em; + left: 1em; + width: 1.1em; + height: 1.1em; +} + +video::-webkit-media-controls-closed-captions-container li.selected .checkmark-container, +audio::-webkit-media-controls-closed-captions-container li.selected .checkmark-container { + display: inline-block; + content: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-closed-captions-container li.selected:hover .checkmark-container, +audio::-webkit-media-controls-closed-captions-container li.selected:hover .checkmark-container { + content: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-panel .picture-in-picture-button { + margin-right: 7px; + margin-left: 8px; + margin-top: 1px; + width: 16px; + min-width: 18px; + height: 12px; + + background-image: url('data:image/svg+xml,'); + } + +video::-webkit-media-controls-panel .picture-in-picture-button.return-from-picture-in-picture { + background-image: url('data:image/svg+xml,'); + } + +video::-webkit-media-controls-fullscreen-button, +audio::-webkit-media-controls-fullscreen-button { + background-image: url('data:image/svg+xml,'); + margin-right: 7px; + margin-left: 8px; + width: 14px; + min-width: 14px; +} + +video::-webkit-media-controls-fullscreen-button.exit, +audio::-webkit-media-controls-fullscreen-button.exit { + background-image: url('data:image/svg+xml,'); + margin-right: 11px; + margin-left: 0px; + margin-top: 6px; +} + +video::-webkit-media-controls-status-display, +audio::-webkit-media-controls-status-display { + cursor: default; + overflow: hidden; + color: rgb(156, 156, 156); + opacity: 0.99; + + letter-spacing: normal; + word-spacing: normal; + line-height: 25px; + text-transform: none; + text-indent: 0; + text-decoration: none; + text-align: left; + + padding: 0 12px; + + -webkit-flex: 1 1 0; +} + +video::-webkit-media-controls-status-display { + color: white; + opacity: .45; + mix-blend-mode: plus-lighter; +} + +video::-webkit-media-controls-timeline, +audio::-webkit-media-controls-timeline { + -webkit-appearance: none !important; + -webkit-flex: 1 1 0 !important; + height: 17px !important; + margin: 0 !important; + background-size: 100% 100% !important; + background-repeat: no-repeat; + background-color: transparent; +} + +video::-webkit-media-controls-timeline { + mix-blend-mode: plus-lighter; +} + +video::-webkit-media-controls-timeline::-webkit-slider-thumb, +audio::-webkit-media-controls-timeline::-webkit-slider-thumb { + -webkit-appearance: none !important; + width: 3px !important; + height: 15px !important; +} + +video::-webkit-media-controls-current-time-display, +video::-webkit-media-controls-time-remaining-display, +audio::-webkit-media-controls-current-time-display, +audio::-webkit-media-controls-time-remaining-display { + -webkit-user-select: none; + -webkit-flex: 0 0 0; + display: -webkit-flex; + cursor: default; + overflow: hidden; + color: rgb(156, 156, 156); + letter-spacing: normal; + word-spacing: normal; + line-height: normal; + text-transform: none; + text-indent: 0px; + text-decoration: none; + position: relative; + bottom: 0.5px; + font-family: -apple-system-monospaced-numbers; + font-size: 11px !important; + font-style: normal !important; + font-weight: normal !important; + -webkit-text-size-adjust: none; +} + +video::-webkit-media-controls-current-time-display, +video::-webkit-media-controls-time-remaining-display { + color: white; + opacity: .45; + mix-blend-mode: plus-lighter; +} + +video::-webkit-media-controls-current-time-display, +audio::-webkit-media-controls-current-time-display { + margin-left: 8px; + margin-right: 8px; + width: 32px; + min-width: 32px; + -webkit-justify-content: flex-end; +} + +video::-webkit-media-controls-time-remaining-display, +audio::-webkit-media-controls-time-remaining-display { + margin-left: 8px; + margin-right: 8px; + width: 37px; + min-width: 37px; + -webkit-justify-content: flex-start; +} + +video::-webkit-media-controls-time-remaining-display.five-digit-time, +audio::-webkit-media-controls-time-remaining-display.five-digit-time { + min-width: 47px; +} + +video::-webkit-media-controls-current-time-display.five-digit-time, +audio::-webkit-media-controls-current-time-display.five-digit-time { + min-width: 42px; +} + +video::-webkit-media-controls-time-remaining-display.six-digit-time, +audio::-webkit-media-controls-time-remaining-display.six-digit-time { + min-width: 54px; +} + +video::-webkit-media-controls-current-time-display.six-digit-time, +audio::-webkit-media-controls-current-time-display.six-digit-time { + min-width: 49px; +} + +video::-webkit-media-controls-timeline-container, +audio::-webkit-media-controls-timeline-container { + display: -webkit-flex; + -webkit-flex-direction: row; + -webkit-align-items: center; + -webkit-user-select: none; + -webkit-flex: 1 1 0; + min-width: 0; + position: relative; + padding: 0; +} + +video::-webkit-media-controls-panel .thumbnail-track, +audio::-webkit-media-controls-panel .thumbnail-track { + position: relative; + -webkit-flex: 1 1 0; + min-width: 0; + height: 17px; + margin: 0 2px; + display: -webkit-flex; + -webkit-align-items: stretch; + -webkit-flex-direction: column; +} + +video::-webkit-media-controls-panel .thumbnail, +audio::-webkit-media-controls-panel .thumbnail { + position: absolute; + opacity: 0; + transition: opacity 0.25s linear; + bottom: 15px; + width: 100px; + height: 58px; + margin-left: -50px; + border: 5px solid black; + box-shadow: 0 0 3px white; + border-radius: 3px; +} + +video::-webkit-media-controls-panel .thumbnail-image, +audio::-webkit-media-controls-panel .thumbnail-image { + width: 100%; + height: 100%; +} + +video::-webkit-media-controls-panel .thumbnail.show, +audio::-webkit-media-controls-panel .thumbnail.show { + opacity: 1; +} + +video::-webkit-media-controls-panel.hidden, +audio::-webkit-media-controls-panel.hidden { + display: none; +} + +video::-webkit-media-controls-panel .hidden, +audio::-webkit-media-controls-panel .hidden, +video::-webkit-media-controls-panel .dropped, +audio::-webkit-media-controls-panel .dropped { + display: none; +} + +/* Full Screen */ + +/* + Page stylesheets are not allowed to override the style of media + controls while in full screen mode, so many if not all the rules + defined in this section will be marked as !important to enforce + this restriction +*/ + +video:-webkit-full-screen::-webkit-media-controls-panel { + -webkit-align-items: flex-start !important; + -webkit-justify-content: flex-end !important; + -webkit-clip-path: inset(20px round 6px); + + width: 480px !important; + height: 104px !important; + margin: 0 auto 12px auto !important; + padding: 20px; + padding-top: 30px !important; + + background-color: transparent; + border-radius: 6px !important; + + transition: opacity 0.3s linear !important; +} + +video:-webkit-full-screen::-webkit-media-controls-panel-tint, +audio:-webkit-full-screen::-webkit-media-controls-panel-tint { + position: absolute; + left: 0; + top: 0; + width: 480px !important; + height: 104px !important; + border-radius: 6px !important; + background-color: rgb(41, 41, 41); + mix-blend-mode: lighten; +} + +video:-webkit-full-screen::-webkit-media-controls-panel-background, +audio:-webkit-full-screen::-webkit-media-controls-panel-background { + position: absolute; + left: 0; + top: 0; + width: 480px !important; + height: 104px !important; + border-radius: 6px !important; + background-color: rgba(30, 30, 30, 0.45); + -webkit-backdrop-filter: saturate(180%) blur(20px); +} + +video:-webkit-animating-full-screen-transition::-webkit-media-controls-panel { + opacity: 0 ! important; + transition: opacity 0 ! important; +} + +video:-webkit-full-screen::-webkit-media-controls-panel .volume-box { + -webkit-transform: none; + -webkit-clip-path: none; + opacity: 1; + left: 32px; + top: 35px; + width: 96px; + height: 17px; + display: -webkit-flex; + -webkit-flex-direction: row; + -webkit-align-items: center; + background-color: transparent; + border: none; +} + +video:-webkit-full-screen::-webkit-media-controls-panel .volume-box:not(.uses-ltr-user-interface-layout-direction) { + transform: translateX(23px) scaleX(-1); +} + +video:-webkit-full-screen::-webkit-media-controls-volume-slider { + -webkit-transform: none; + background-color: transparent; + min-width: 60px !important; + width: 60px !important; + height: 9px !important; + margin: 0 !important; +} + +video:-webkit-full-screen::-webkit-media-controls-mute-button, +audio:-webkit-full-screen::-webkit-media-controls-mute-button, +video:-webkit-full-screen::-webkit-media-controls-volume-max-button { + width: 14px !important; + margin-left: 6px !important; + margin-bottom: 2px !important; + background-image: url('data:image/svg+xml,'); +} +video:-webkit-full-screen::-webkit-media-controls-mute-button, +audio:-webkit-full-screen::-webkit-media-controls-mute-button, +video:-webkit-full-screen::-webkit-media-controls-volume-min-button { + width: 14px !important; + margin-right: 2px !important; + margin-bottom: 2px !important; + background-image: url('data:image/svg+xml,'); +} + +video:-webkit-full-screen::-webkit-media-controls-play-button { + position: absolute; + + background-image: url('data:image/svg+xml,'); + + width: 21px; + height: 23px; + left: 230px; + top: 32px; + margin: 0; + padding: 0; +} + +video:-webkit-full-screen::-webkit-media-controls-play-button.paused { + background-image: url('data:image/svg+xml,'); +} + +video:-webkit-full-screen::-webkit-media-controls-seek-back-button { + position: absolute; + + background-image: url('data:image/svg+xml,'); + + width: 24px; + height: 15px; + left: 176px; + top: 36px; +} + +video:-webkit-full-screen::-webkit-media-controls-return-to-realtime-button { + position: absolute; + display: -webkit-flex; + width: 29px; + height: 16px; + left: 262px; + top: 13px; +} + +video:-webkit-full-screen::-webkit-media-controls-seek-forward-button { + position: absolute; + + background-image: url('data:image/svg+xml,'); + + width: 24px; + height: 15px; + left: 275px; + top: 36px; +} + +video:-webkit-full-screen::-webkit-media-controls-timeline-container { + height: auto; + width: 440px; + position: absolute; + bottom: 28px; + left: 20px; +} + +video:-webkit-full-screen::-webkit-media-controls-current-time-display { + margin-left: 12px; +} + +video:-webkit-full-screen::-webkit-media-controls-time-remaining-display { + margin-right: 12px; +} + +video:-webkit-full-screen::-webkit-media-controls-status-display { + width: 440px; + position: absolute; + bottom: 25px; + text-align: center; + padding: 0; + left: 20px; +} + +video:-webkit-full-screen::-webkit-media-controls-toggle-closed-captions-button, +audio:-webkit-full-screen::-webkit-media-controls-toggle-closed-captions-button { + margin-top: 6px; + margin-right:24px; + margin-left: 0px; +} + +video:-webkit-full-screen::-webkit-media-controls-wireless-playback-picker-button, +audio:-webkit-full-screen::-webkit-media-controls-wireless-playback-picker-button { + margin-top: 6px; + margin-right:24px; + margin-left: 0px; +} + +video:-webkit-full-screen::-webkit-media-controls-closed-captions-container { + bottom: 100px; + right: calc(50% - 183px); /* 183px is 221px (half the media controller's width) minus 38px (the right position of the captions icon). */ + max-width: calc(50% + 173px); /* right + 10px */ + max-height: calc(100% - 124px); /* bottom + 10px */ +} + +video::-webkit-media-text-track-container { + position: relative; + width: 100%; + overflow: hidden; + padding-bottom: 5px; + z-index: 0; + + text-align: center; + color: rgba(255, 255, 255, 1); + + letter-spacing: normal; + word-spacing: normal; + text-transform: none; + text-indent: 0; + text-decoration: none; + pointer-events: none; + -webkit-user-select: none; + + -webkit-flex: 1 1; + + -webkit-line-box-contain: block inline-box replaced; +} + +video::cue { + background-color: rgba(0, 0, 0, 0.8); +} + +video::-webkit-media-text-track-display { + position: absolute; + overflow: hidden; + white-space: pre-wrap; + -webkit-box-sizing: border-box; + font: 22px sans-serif; +} + +video::-webkit-media-text-track-display-backdrop { + display: inline-block; +} + +video::cue(:future) { + color: gray; +} + +video::-webkit-media-text-track-container b { + font-weight: bold; +} + +video::-webkit-media-text-track-container u { + text-decoration: underline; +} + +video::-webkit-media-text-track-container i { + font-style: italic; +} + +video::-webkit-media-text-track-container .hidden, +audio::-webkit-media-text-track-container .hidden { + display: none; +} + +/* ============ ACTIVE VERSIONS OF ALL BUTTONS ============= */ + +video::-webkit-media-controls-mute-button:active, +audio::-webkit-media-controls-mute-button:active, +video::-webkit-media-controls-volume-max-button:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-panel button.muted:active, +audio::-webkit-media-controls-panel button.muted:active, +video::-webkit-media-controls-volume-min-button:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-toggle-closed-captions-button:active, +audio::-webkit-media-controls-toggle-closed-captions-button:active { + background-image: url('data:image/svg+xml, '); +} + +video::-webkit-media-controls-rewind-button:active, +audio::-webkit-media-controls-rewind-button:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-panel button.paused:active, +audio::-webkit-media-controls-panel button.paused:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-play-button:active, +audio::-webkit-media-controls-play-button:active { + background-image: url('data:image/svg+xml,'); +} + +video:-webkit-full-screen::-webkit-media-controls-mute-button:active, +audio:-webkit-full-screen::-webkit-media-controls-mute-button:active, +video:-webkit-full-screen::-webkit-media-controls-volume-max-button:active { + background-image: url('data:image/svg+xml,'); +} +video:-webkit-full-screen::-webkit-media-controls-mute-button:active, +audio:-webkit-full-screen::-webkit-media-controls-mute-button:active, +video:-webkit-full-screen::-webkit-media-controls-volume-min-button:active { + background-image: url('data:image/svg+xml,'); +} + +video:-webkit-full-screen::-webkit-media-controls-play-button:active{ + background-image: url('data:image/svg+xml,'); +} + +video:-webkit-full-screen::-webkit-media-controls-panel button.paused:active { + background-image: url('data:image/svg+xml,'); +} + +video:-webkit-full-screen::-webkit-media-controls-seek-back-button:active { + background-image: url('data:image/svg+xml,'); +} + +video:-webkit-full-screen::-webkit-media-controls-seek-forward-button:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-fullscreen-button:active, +audio::-webkit-media-controls-fullscreen-button:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-panel button.exit:active, +audio::-webkit-media-controls-panel button.exit:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-wireless-playback-picker-button:active, +audio::-webkit-media-controls-wireless-playback-picker-button:active { + background-image: url('data:image/svg+xml, '); +} + +video::-webkit-media-controls-panel .picture-in-picture-button:active { + background-image: url('data:image/svg+xml,'); +} + +video::-webkit-media-controls-panel .picture-in-picture-button.return-from-picture-in-picture:active { + background-image: url('data:image/svg+xml,'); +} + +/* ==================== AIRPLAY PLACARD ==================== */ + +video::-webkit-media-controls-wireless-playback-status, +audio::-webkit-media-controls-wireless-playback-status { + display: block; + position: absolute; + left: 0px; + top: 0px; + width: 100%; + height: 100%; + background-image: url('data:image/svg+xml;utf8,'); + background-color: rgb(51, 51, 53); + background-repeat: no-repeat; + background-position: 50% calc(.5 * (100% - 25px) - 21pt); + background-size: 131px auto; + color: -apple-system-gray; + font-family: -apple-system; + vertical-align: text-bottom; +} + +video::-webkit-media-controls-wireless-playback-text, +audio::-webkit-media-controls-wireless-playback-text { + cursor: default; + position: absolute; + width: 100%; + top: calc(.5 * (100% - 25px) + (.5 * (90px + 42pt) - 42pt)); + -webkit-user-select: none; + margin: 0px; + height: 42pt; +} + +video::-webkit-media-controls-wireless-playback-text-top, +audio::-webkit-media-controls-wireless-playback-text-top { + position: absolute; + top: 15pt; + width: 100%; + line-height: 12pt; + height: 13pt; + font-size: 12pt; + text-align: center; + margin: 0px; +} + +video::-webkit-media-controls-wireless-playback-text-bottom, +audio::-webkit-media-controls-wireless-playback-text-bottom { + position: absolute; + bottom: 0; + left: 5%; + width: 90%; + line-height: 10pt; + height: 11pt; + font-size: 10pt; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 0px; +} + +video::-webkit-media-controls-wireless-playback-status.small, +audio::-webkit-media-controls-wireless-playback-status.small { + background-image: url('data:image/svg+xml;utf8,'); + background-position: 50% calc(.5 * (100% - 25px) - 5pt); + background-size: 62px auto; +} + +video::-webkit-media-controls-wireless-playback-text-top.small, +audio::-webkit-media-controls-wireless-playback-text-top.small { + top: 4pt; +} + +video::-webkit-media-controls-wireless-playback-text-bottom.small, +audio::-webkit-media-controls-wireless-playback-text-bottom.small { + display: none; +} + +video::-webkit-media-controls-wireless-playback-status.picture-in-picture, +audio::-webkit-media-controls-wireless-playback-status.picture-in-picture +{ + background-size: 304px auto; + background-position: 50% calc(.5 * (100% - 25px)); + background-image: url('data:image/svg+xml;utf8,'); +} + +video::-webkit-media-controls-wireless-playback-text-top.picture-in-picture, +audio::-webkit-media-controls-wireless-playback-text-top.picture-in-picture { + top: initial; + bottom: 0; +} + +video::-webkit-media-controls-wireless-playback-text-bottom.picture-in-picture, +audio::-webkit-media-controls-wireless-playback-text-bottom.picture-in-picture { + display: none; +} + +video::-webkit-media-controls-wireless-playback-status.hidden, +audio::-webkit-media-controls-wireless-playback-status.hidden { + display: none; +} + +video::-webkit-media-controls-panel.picture-in-picture { + opacity: 0; + pointer-events: none; +} + +video:-webkit-full-screen::-webkit-media-controls-panel .picture-in-picture-button { + margin-top: 7px; + margin-right: 24px; + margin-left: 0px; +} + +/* Time display clones that we use in updateLayoutForDisplayedWidth(). */ +::-webkit-media-controls-current-time-display.clone, +::-webkit-media-controls-time-remaining-display.clone { + position: absolute; + display: inline; + top: 100%; + mix-blend-mode: normal; +} diff --git a/Source/WebCore/Modules/mediacontrols/mediaControlsApple.js b/Source/WebCore/Modules/mediacontrols/mediaControlsApple.js new file mode 100644 index 000000000..d1893734c --- /dev/null +++ b/Source/WebCore/Modules/mediacontrols/mediaControlsApple.js @@ -0,0 +1,2509 @@ +function createControls(root, video, host) +{ + return new Controller(root, video, host); +}; + +function Controller(root, video, host) +{ + this.video = video; + this.root = root; + this.host = host; + this.controls = {}; + this.listeners = {}; + this.isLive = false; + this.statusHidden = true; + this.hasWirelessPlaybackTargets = false; + this.canToggleShowControlsButton = false; + this.isListeningForPlaybackTargetAvailabilityEvent = false; + this.currentTargetIsWireless = false; + this.wirelessPlaybackDisabled = false; + this.isVolumeSliderActive = false; + this.currentDisplayWidth = 0; + this._scrubbing = false; + this._pageScaleFactor = 1; + + this.addVideoListeners(); + this.createBase(); + this.createControls(); + this.createTimeClones(); + this.updateBase(); + this.updateControls(); + this.updateDuration(); + this.updateProgress(); + this.updateTime(); + this.updateReadyState(); + this.updatePlaying(); + this.updateThumbnail(); + this.updateCaptionButton(); + this.updateCaptionContainer(); + this.updateFullscreenButtons(); + this.updateVolume(); + this.updateHasAudio(); + this.updateHasVideo(); + this.updateWirelessTargetAvailable(); + this.updateWirelessPlaybackStatus(); + this.updatePictureInPicturePlaceholder(); + this.scheduleUpdateLayoutForDisplayedWidth(); + + this.listenFor(this.root, 'resize', this.handleRootResize); +}; + +/* Enums */ +Controller.InlineControls = 0; +Controller.FullScreenControls = 1; + +Controller.PlayAfterSeeking = 0; +Controller.PauseAfterSeeking = 1; + +/* Globals */ +Controller.gSimulateWirelessPlaybackTarget = false; // Used for testing when there are no wireless targets. +Controller.gSimulatePictureInPictureAvailable = false; // Used for testing when picture-in-picture is not available. + +Controller.prototype = { + + /* Constants */ + HandledVideoEvents: { + loadstart: 'handleLoadStart', + error: 'handleError', + abort: 'handleAbort', + suspend: 'handleSuspend', + stalled: 'handleStalled', + waiting: 'handleWaiting', + emptied: 'handleReadyStateChange', + loadedmetadata: 'handleReadyStateChange', + loadeddata: 'handleReadyStateChange', + canplay: 'handleReadyStateChange', + canplaythrough: 'handleReadyStateChange', + timeupdate: 'handleTimeUpdate', + durationchange: 'handleDurationChange', + playing: 'handlePlay', + pause: 'handlePause', + progress: 'handleProgress', + volumechange: 'handleVolumeChange', + webkitfullscreenchange: 'handleFullscreenChange', + webkitbeginfullscreen: 'handleFullscreenChange', + webkitendfullscreen: 'handleFullscreenChange', + }, + PlaceholderPollingDelay: 33, + HideControlsDelay: 4 * 1000, + RewindAmount: 30, + MaximumSeekRate: 8, + SeekDelay: 1500, + ClassNames: { + active: 'active', + dropped: 'dropped', + exit: 'exit', + failed: 'failed', + hidden: 'hidden', + hiding: 'hiding', + threeDigitTime: 'three-digit-time', + fourDigitTime: 'four-digit-time', + fiveDigitTime: 'five-digit-time', + sixDigitTime: 'six-digit-time', + list: 'list', + muteBox: 'mute-box', + muted: 'muted', + paused: 'paused', + pictureInPicture: 'picture-in-picture', + playing: 'playing', + returnFromPictureInPicture: 'return-from-picture-in-picture', + selected: 'selected', + show: 'show', + small: 'small', + thumbnail: 'thumbnail', + thumbnailImage: 'thumbnail-image', + thumbnailTrack: 'thumbnail-track', + volumeBox: 'volume-box', + noVideo: 'no-video', + down: 'down', + out: 'out', + pictureInPictureButton: 'picture-in-picture-button', + placeholderShowing: 'placeholder-showing', + usesLTRUserInterfaceLayoutDirection: 'uses-ltr-user-interface-layout-direction', + appleTV: 'appletv', + }, + KeyCodes: { + enter: 13, + escape: 27, + space: 32, + pageUp: 33, + pageDown: 34, + end: 35, + home: 36, + left: 37, + up: 38, + right: 39, + down: 40 + }, + MinimumTimelineWidth: 80, + ButtonWidth: 32, + + extend: function(child) + { + // This function doesn't actually do what we want it to. In particular it + // is not copying the getters and setters to the child class, since they are + // not enumerable. What we should do is use ES6 classes, or assign the __proto__ + // directly. + // FIXME: Use ES6 classes. + + for (var property in this) { + if (!child.hasOwnProperty(property)) + child[property] = this[property]; + } + }, + + get idiom() + { + return "apple"; + }, + + UIString: function(developmentString, replaceString, replacementString) + { + var localized = UIStringTable[developmentString]; + if (replaceString && replacementString) + return localized.replace(replaceString, replacementString); + + if (localized) + return localized; + + console.error("Localization for string \"" + developmentString + "\" not found."); + return "LOCALIZED STRING NOT FOUND"; + }, + + listenFor: function(element, eventName, handler, useCapture) + { + if (typeof useCapture === 'undefined') + useCapture = false; + + if (!(this.listeners[eventName] instanceof Array)) + this.listeners[eventName] = []; + this.listeners[eventName].push({element:element, handler:handler, useCapture:useCapture}); + element.addEventListener(eventName, this, useCapture); + }, + + stopListeningFor: function(element, eventName, handler, useCapture) + { + if (typeof useCapture === 'undefined') + useCapture = false; + + if (!(this.listeners[eventName] instanceof Array)) + return; + + this.listeners[eventName] = this.listeners[eventName].filter(function(entry) { + return !(entry.element === element && entry.handler === handler && entry.useCapture === useCapture); + }); + element.removeEventListener(eventName, this, useCapture); + }, + + addVideoListeners: function() + { + for (var name in this.HandledVideoEvents) { + this.listenFor(this.video, name, this.HandledVideoEvents[name]); + }; + + /* text tracks */ + this.listenFor(this.video.textTracks, 'change', this.handleTextTrackChange); + this.listenFor(this.video.textTracks, 'addtrack', this.handleTextTrackAdd); + this.listenFor(this.video.textTracks, 'removetrack', this.handleTextTrackRemove); + + /* audio tracks */ + this.listenFor(this.video.audioTracks, 'change', this.handleAudioTrackChange); + this.listenFor(this.video.audioTracks, 'addtrack', this.handleAudioTrackAdd); + this.listenFor(this.video.audioTracks, 'removetrack', this.handleAudioTrackRemove); + + /* video tracks */ + this.listenFor(this.video.videoTracks, 'change', this.updateHasVideo); + this.listenFor(this.video.videoTracks, 'addtrack', this.updateHasVideo); + this.listenFor(this.video.videoTracks, 'removetrack', this.updateHasVideo); + + /* controls attribute */ + this.controlsObserver = new MutationObserver(this.handleControlsChange.bind(this)); + this.controlsObserver.observe(this.video, { attributes: true, attributeFilter: ['controls'] }); + + this.listenFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange); + + if ('webkitPresentationMode' in this.video) + this.listenFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange); + }, + + removeVideoListeners: function() + { + for (var name in this.HandledVideoEvents) { + this.stopListeningFor(this.video, name, this.HandledVideoEvents[name]); + }; + + /* text tracks */ + this.stopListeningFor(this.video.textTracks, 'change', this.handleTextTrackChange); + this.stopListeningFor(this.video.textTracks, 'addtrack', this.handleTextTrackAdd); + this.stopListeningFor(this.video.textTracks, 'removetrack', this.handleTextTrackRemove); + + /* audio tracks */ + this.stopListeningFor(this.video.audioTracks, 'change', this.handleAudioTrackChange); + this.stopListeningFor(this.video.audioTracks, 'addtrack', this.handleAudioTrackAdd); + this.stopListeningFor(this.video.audioTracks, 'removetrack', this.handleAudioTrackRemove); + + /* video tracks */ + this.stopListeningFor(this.video.videoTracks, 'change', this.updateHasVideo); + this.stopListeningFor(this.video.videoTracks, 'addtrack', this.updateHasVideo); + this.stopListeningFor(this.video.videoTracks, 'removetrack', this.updateHasVideo); + + /* controls attribute */ + this.controlsObserver.disconnect(); + delete(this.controlsObserver); + + this.stopListeningFor(this.video, 'webkitcurrentplaybacktargetiswirelesschanged', this.handleWirelessPlaybackChange); + this.setShouldListenForPlaybackTargetAvailabilityEvent(false); + + if ('webkitPresentationMode' in this.video) + this.stopListeningFor(this.video, 'webkitpresentationmodechanged', this.handlePresentationModeChange); + }, + + handleEvent: function(event) + { + var preventDefault = false; + + try { + if (event.target === this.video) { + var handlerName = this.HandledVideoEvents[event.type]; + var handler = this[handlerName]; + if (handler && handler instanceof Function) + handler.call(this, event); + } + + if (!(this.listeners[event.type] instanceof Array)) + return; + + this.listeners[event.type].forEach(function(entry) { + if (entry.element === event.currentTarget && entry.handler instanceof Function) + preventDefault |= entry.handler.call(this, event); + }, this); + } catch(e) { + if (window.console) + console.error(e); + } + + if (preventDefault) { + event.stopPropagation(); + event.preventDefault(); + } + }, + + createBase: function() + { + var base = this.base = document.createElement('div'); + base.setAttribute('pseudo', '-webkit-media-controls'); + this.listenFor(base, 'mousemove', this.handleWrapperMouseMove); + this.listenFor(this.video, 'mouseout', this.handleWrapperMouseOut); + if (this.host.textTrackContainer) + base.appendChild(this.host.textTrackContainer); + }, + + shouldHaveAnyUI: function() + { + return this.shouldHaveControls() || (this.video.textTracks && this.video.textTracks.length) || this.currentPlaybackTargetIsWireless(); + }, + + shouldShowControls: function() + { + if (!this.isAudio() && !this.host.allowsInlineMediaPlayback) + return true; + + return this.video.controls || this.isFullScreen(); + }, + + shouldHaveControls: function() + { + return this.shouldShowControls() || this.isFullScreen() || this.presentationMode() === 'picture-in-picture' || this.currentPlaybackTargetIsWireless(); + }, + + + setNeedsTimelineMetricsUpdate: function() + { + this.timelineMetricsNeedsUpdate = true; + }, + + scheduleUpdateLayoutForDisplayedWidth: function() + { + setTimeout(this.updateLayoutForDisplayedWidth.bind(this), 0); + }, + + updateTimelineMetricsIfNeeded: function() + { + if (this.timelineMetricsNeedsUpdate && !this.controlsAreHidden()) { + this.timelineLeft = this.controls.timeline.offsetLeft; + this.timelineWidth = this.controls.timeline.offsetWidth; + this.timelineHeight = this.controls.timeline.offsetHeight; + this.timelineMetricsNeedsUpdate = false; + } + }, + + updateBase: function() + { + if (this.shouldHaveAnyUI()) { + if (!this.base.parentNode) { + this.root.appendChild(this.base); + } + } else { + if (this.base.parentNode) { + this.base.parentNode.removeChild(this.base); + } + } + }, + + createControls: function() + { + var panel = this.controls.panel = document.createElement('div'); + panel.setAttribute('pseudo', '-webkit-media-controls-panel'); + panel.setAttribute('aria-label', (this.isAudio() ? this.UIString('Audio Playback') : this.UIString('Video Playback'))); + panel.setAttribute('role', 'toolbar'); + this.listenFor(panel, 'mousedown', this.handlePanelMouseDown); + this.listenFor(panel, 'transitionend', this.handlePanelTransitionEnd); + this.listenFor(panel, 'click', this.handlePanelClick); + this.listenFor(panel, 'dblclick', this.handlePanelClick); + this.listenFor(panel, 'dragstart', this.handlePanelDragStart); + + var panelBackgroundContainer = this.controls.panelBackgroundContainer = document.createElement('div'); + panelBackgroundContainer.setAttribute('pseudo', '-webkit-media-controls-panel-background-container'); + + var panelTint = this.controls.panelTint = document.createElement('div'); + panelTint.setAttribute('pseudo', '-webkit-media-controls-panel-tint'); + this.listenFor(panelTint, 'mousedown', this.handlePanelMouseDown); + this.listenFor(panelTint, 'transitionend', this.handlePanelTransitionEnd); + this.listenFor(panelTint, 'click', this.handlePanelClick); + this.listenFor(panelTint, 'dblclick', this.handlePanelClick); + this.listenFor(panelTint, 'dragstart', this.handlePanelDragStart); + + var panelBackground = this.controls.panelBackground = document.createElement('div'); + panelBackground.setAttribute('pseudo', '-webkit-media-controls-panel-background'); + + var rewindButton = this.controls.rewindButton = document.createElement('button'); + rewindButton.setAttribute('pseudo', '-webkit-media-controls-rewind-button'); + rewindButton.setAttribute('aria-label', this.UIString('Rewind ##sec## Seconds', '##sec##', this.RewindAmount)); + this.listenFor(rewindButton, 'click', this.handleRewindButtonClicked); + + var seekBackButton = this.controls.seekBackButton = document.createElement('button'); + seekBackButton.setAttribute('pseudo', '-webkit-media-controls-seek-back-button'); + seekBackButton.setAttribute('aria-label', this.UIString('Rewind')); + this.listenFor(seekBackButton, 'mousedown', this.handleSeekBackMouseDown); + this.listenFor(seekBackButton, 'mouseup', this.handleSeekBackMouseUp); + + var seekForwardButton = this.controls.seekForwardButton = document.createElement('button'); + seekForwardButton.setAttribute('pseudo', '-webkit-media-controls-seek-forward-button'); + seekForwardButton.setAttribute('aria-label', this.UIString('Fast Forward')); + this.listenFor(seekForwardButton, 'mousedown', this.handleSeekForwardMouseDown); + this.listenFor(seekForwardButton, 'mouseup', this.handleSeekForwardMouseUp); + + var playButton = this.controls.playButton = document.createElement('button'); + playButton.setAttribute('pseudo', '-webkit-media-controls-play-button'); + playButton.setAttribute('aria-label', this.UIString('Play')); + this.listenFor(playButton, 'click', this.handlePlayButtonClicked); + + var statusDisplay = this.controls.statusDisplay = document.createElement('div'); + statusDisplay.setAttribute('pseudo', '-webkit-media-controls-status-display'); + statusDisplay.classList.add(this.ClassNames.hidden); + + var timelineBox = this.controls.timelineBox = document.createElement('div'); + timelineBox.setAttribute('pseudo', '-webkit-media-controls-timeline-container'); + + var currentTime = this.controls.currentTime = document.createElement('div'); + currentTime.setAttribute('pseudo', '-webkit-media-controls-current-time-display'); + currentTime.setAttribute('aria-label', this.UIString('Elapsed')); + currentTime.setAttribute('role', 'timer'); + + var timeline = this.controls.timeline = document.createElement('input'); + timeline.setAttribute('pseudo', '-webkit-media-controls-timeline'); + timeline.setAttribute('aria-label', this.UIString('Duration')); + timeline.type = 'range'; + timeline.value = 0; + this.listenFor(timeline, 'input', this.handleTimelineInput); + this.listenFor(timeline, 'change', this.handleTimelineChange); + this.listenFor(timeline, 'mouseover', this.handleTimelineMouseOver); + this.listenFor(timeline, 'mouseout', this.handleTimelineMouseOut); + this.listenFor(timeline, 'mousemove', this.handleTimelineMouseMove); + this.listenFor(timeline, 'mousedown', this.handleTimelineMouseDown); + this.listenFor(timeline, 'mouseup', this.handleTimelineMouseUp); + this.listenFor(timeline, 'keydown', this.handleTimelineKeyDown); + timeline.step = .01; + + this.timelineContextName = "_webkit-media-controls-timeline-" + this.host.generateUUID(); + timeline.style.backgroundImage = '-webkit-canvas(' + this.timelineContextName + ')'; + + var thumbnailTrack = this.controls.thumbnailTrack = document.createElement('div'); + thumbnailTrack.classList.add(this.ClassNames.thumbnailTrack); + + var thumbnail = this.controls.thumbnail = document.createElement('div'); + thumbnail.classList.add(this.ClassNames.thumbnail); + + var thumbnailImage = this.controls.thumbnailImage = document.createElement('img'); + thumbnailImage.classList.add(this.ClassNames.thumbnailImage); + + var remainingTime = this.controls.remainingTime = document.createElement('div'); + remainingTime.setAttribute('pseudo', '-webkit-media-controls-time-remaining-display'); + remainingTime.setAttribute('aria-label', this.UIString('Remaining')); + remainingTime.setAttribute('role', 'timer'); + + var muteBox = this.controls.muteBox = document.createElement('div'); + muteBox.classList.add(this.ClassNames.muteBox); + this.listenFor(muteBox, 'mouseover', this.handleMuteBoxOver); + + var muteButton = this.controls.muteButton = document.createElement('button'); + muteButton.setAttribute('pseudo', '-webkit-media-controls-mute-button'); + muteButton.setAttribute('aria-label', this.UIString('Mute')); + // Make the mute button a checkbox since it only has on/off states. + muteButton.setAttribute('role', 'checkbox'); + this.listenFor(muteButton, 'click', this.handleMuteButtonClicked); + + var minButton = this.controls.minButton = document.createElement('button'); + minButton.setAttribute('pseudo', '-webkit-media-controls-volume-min-button'); + minButton.setAttribute('aria-label', this.UIString('Minimum Volume')); + this.listenFor(minButton, 'click', this.handleMinButtonClicked); + + var maxButton = this.controls.maxButton = document.createElement('button'); + maxButton.setAttribute('pseudo', '-webkit-media-controls-volume-max-button'); + maxButton.setAttribute('aria-label', this.UIString('Maximum Volume')); + this.listenFor(maxButton, 'click', this.handleMaxButtonClicked); + + var volumeBox = this.controls.volumeBox = document.createElement('div'); + volumeBox.setAttribute('pseudo', '-webkit-media-controls-volume-slider-container'); + volumeBox.classList.add(this.ClassNames.volumeBox); + + var volumeBoxBackground = this.controls.volumeBoxBackground = document.createElement('div'); + volumeBoxBackground.setAttribute('pseudo', '-webkit-media-controls-volume-slider-container-background'); + + var volumeBoxTint = this.controls.volumeBoxTint = document.createElement('div'); + volumeBoxTint.setAttribute('pseudo', '-webkit-media-controls-volume-slider-container-tint'); + + var volume = this.controls.volume = document.createElement('input'); + volume.setAttribute('pseudo', '-webkit-media-controls-volume-slider'); + volume.setAttribute('aria-label', this.UIString('Volume')); + volume.type = 'range'; + volume.min = 0; + volume.max = 1; + volume.step = .05; + this.listenFor(volume, 'input', this.handleVolumeSliderInput); + this.listenFor(volume, 'change', this.handleVolumeSliderChange); + this.listenFor(volume, 'mousedown', this.handleVolumeSliderMouseDown); + this.listenFor(volume, 'mouseup', this.handleVolumeSliderMouseUp); + + this.volumeContextName = "_webkit-media-controls-volume-" + this.host.generateUUID(); + volume.style.backgroundImage = '-webkit-canvas(' + this.volumeContextName + ')'; + + var captionButton = this.controls.captionButton = document.createElement('button'); + captionButton.setAttribute('pseudo', '-webkit-media-controls-toggle-closed-captions-button'); + captionButton.setAttribute('aria-label', this.UIString('Captions')); + captionButton.setAttribute('aria-haspopup', 'true'); + captionButton.setAttribute('aria-owns', 'audioAndTextTrackMenu'); + this.listenFor(captionButton, 'click', this.handleCaptionButtonClicked); + + var fullscreenButton = this.controls.fullscreenButton = document.createElement('button'); + fullscreenButton.setAttribute('pseudo', '-webkit-media-controls-fullscreen-button'); + fullscreenButton.setAttribute('aria-label', this.UIString('Display Full Screen')); + this.listenFor(fullscreenButton, 'click', this.handleFullscreenButtonClicked); + + var pictureInPictureButton = this.controls.pictureInPictureButton = document.createElement('button'); + pictureInPictureButton.setAttribute('pseudo', '-webkit-media-controls-picture-in-picture-button'); + pictureInPictureButton.setAttribute('aria-label', this.UIString('Display Picture in Picture')); + pictureInPictureButton.classList.add(this.ClassNames.pictureInPictureButton); + this.listenFor(pictureInPictureButton, 'click', this.handlePictureInPictureButtonClicked); + + var inlinePlaybackPlaceholder = this.controls.inlinePlaybackPlaceholder = document.createElement('div'); + inlinePlaybackPlaceholder.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-status'); + inlinePlaybackPlaceholder.setAttribute('aria-label', this.UIString('Video Playback Placeholder')); + this.listenFor(inlinePlaybackPlaceholder, 'click', this.handlePlaceholderClick); + this.listenFor(inlinePlaybackPlaceholder, 'dblclick', this.handlePlaceholderClick); + if (!Controller.gSimulatePictureInPictureAvailable) + inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden); + + var inlinePlaybackPlaceholderText = this.controls.inlinePlaybackPlaceholderText = document.createElement('div'); + inlinePlaybackPlaceholderText.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-text'); + + var inlinePlaybackPlaceholderTextTop = this.controls.inlinePlaybackPlaceholderTextTop = document.createElement('p'); + inlinePlaybackPlaceholderTextTop.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-text-top'); + + var inlinePlaybackPlaceholderTextBottom = this.controls.inlinePlaybackPlaceholderTextBottom = document.createElement('p'); + inlinePlaybackPlaceholderTextBottom.setAttribute('pseudo', '-webkit-media-controls-wireless-playback-text-bottom'); + + 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')); + this.listenFor(wirelessTargetPicker, 'click', this.handleWirelessPickerButtonClicked); + + // Show controls button is an accessibility workaround since the controls are now removed from the DOM. http://webkit.org/b/145684 + var showControlsButton = this.showControlsButton = document.createElement('button'); + showControlsButton.setAttribute('pseudo', '-webkit-media-show-controls'); + this.showShowControlsButton(false); + showControlsButton.setAttribute('aria-label', this.UIString('Show Controls')); + this.listenFor(showControlsButton, 'click', this.handleShowControlsClick); + this.base.appendChild(showControlsButton); + + if (!Controller.gSimulateWirelessPlaybackTarget) + wirelessTargetPicker.classList.add(this.ClassNames.hidden); + }, + + createTimeClones: function() + { + var currentTimeClone = this.currentTimeClone = document.createElement('div'); + currentTimeClone.setAttribute('pseudo', '-webkit-media-controls-current-time-display'); + currentTimeClone.setAttribute('aria-hidden', 'true'); + currentTimeClone.classList.add('clone'); + this.base.appendChild(currentTimeClone); + + var remainingTimeClone = this.remainingTimeClone = document.createElement('div'); + remainingTimeClone.setAttribute('pseudo', '-webkit-media-controls-time-remaining-display'); + remainingTimeClone.setAttribute('aria-hidden', 'true'); + remainingTimeClone.classList.add('clone'); + this.base.appendChild(remainingTimeClone); + }, + + setControlsType: function(type) + { + if (type === this.controlsType) + return; + this.controlsType = type; + + this.reconnectControls(); + this.updateShouldListenForPlaybackTargetAvailabilityEvent(); + }, + + setIsLive: function(live) + { + if (live === this.isLive) + return; + this.isLive = live; + + this.updateStatusDisplay(); + + this.reconnectControls(); + }, + + reconnectControls: function() + { + this.disconnectControls(); + + if (this.controlsType === Controller.InlineControls) + this.configureInlineControls(); + else if (this.controlsType == Controller.FullScreenControls) + this.configureFullScreenControls(); + if (this.shouldHaveControls() || this.currentPlaybackTargetIsWireless()) + this.addControls(); + }, + + disconnectControls: function(event) + { + for (var item in this.controls) { + var control = this.controls[item]; + if (control && control.parentNode) + control.parentNode.removeChild(control); + } + }, + + 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.panelBackgroundContainer); + this.controls.panelBackgroundContainer.appendChild(this.controls.panelBackground); + this.controls.panelBackgroundContainer.appendChild(this.controls.panelTint); + this.controls.panel.appendChild(this.controls.playButton); + if (!this.isLive) + this.controls.panel.appendChild(this.controls.rewindButton); + this.controls.panel.appendChild(this.controls.statusDisplay); + if (!this.isLive) { + this.controls.panel.appendChild(this.controls.timelineBox); + this.controls.timelineBox.appendChild(this.controls.currentTime); + this.controls.timelineBox.appendChild(this.controls.thumbnailTrack); + this.controls.thumbnailTrack.appendChild(this.controls.timeline); + this.controls.thumbnailTrack.appendChild(this.controls.thumbnail); + this.controls.thumbnail.appendChild(this.controls.thumbnailImage); + this.controls.timelineBox.appendChild(this.controls.remainingTime); + } + this.controls.panel.appendChild(this.controls.muteBox); + this.controls.muteBox.appendChild(this.controls.volumeBox); + this.controls.volumeBox.appendChild(this.controls.volumeBoxBackground); + this.controls.volumeBox.appendChild(this.controls.volumeBoxTint); + this.controls.volumeBox.appendChild(this.controls.volume); + this.controls.muteBox.appendChild(this.controls.muteButton); + this.controls.panel.appendChild(this.controls.wirelessTargetPicker); + this.controls.panel.appendChild(this.controls.captionButton); + if (!this.isAudio()) { + this.updatePictureInPictureButton(); + this.controls.panel.appendChild(this.controls.fullscreenButton); + } + + this.controls.panel.style.removeProperty('left'); + this.controls.panel.style.removeProperty('top'); + this.controls.panel.style.removeProperty('bottom'); + }, + + configureFullScreenControls: 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.panelBackground); + this.controls.panel.appendChild(this.controls.panelTint); + this.controls.panel.appendChild(this.controls.volumeBox); + this.controls.volumeBox.appendChild(this.controls.minButton); + this.controls.volumeBox.appendChild(this.controls.volume); + this.controls.volumeBox.appendChild(this.controls.maxButton); + this.controls.panel.appendChild(this.controls.seekBackButton); + this.controls.panel.appendChild(this.controls.playButton); + this.controls.panel.appendChild(this.controls.seekForwardButton); + this.controls.panel.appendChild(this.controls.wirelessTargetPicker); + this.controls.panel.appendChild(this.controls.captionButton); + if (!this.isAudio()) { + this.updatePictureInPictureButton(); + this.controls.panel.appendChild(this.controls.fullscreenButton); + } + if (!this.isLive) { + this.controls.panel.appendChild(this.controls.timelineBox); + this.controls.timelineBox.appendChild(this.controls.currentTime); + this.controls.timelineBox.appendChild(this.controls.thumbnailTrack); + this.controls.thumbnailTrack.appendChild(this.controls.timeline); + this.controls.thumbnailTrack.appendChild(this.controls.thumbnail); + this.controls.thumbnail.appendChild(this.controls.thumbnailImage); + this.controls.timelineBox.appendChild(this.controls.remainingTime); + } else + this.controls.panel.appendChild(this.controls.statusDisplay); + }, + + updateControls: function() + { + if (this.isFullScreen()) + this.setControlsType(Controller.FullScreenControls); + else + this.setControlsType(Controller.InlineControls); + + this.setNeedsUpdateForDisplayedWidth(); + this.updateLayoutForDisplayedWidth(); + this.setNeedsTimelineMetricsUpdate(); + + if (this.shouldShowControls()) { + this.controls.panel.classList.add(this.ClassNames.show); + this.controls.panel.classList.remove(this.ClassNames.hidden); + this.resetHideControlsTimer(); + this.showShowControlsButton(false); + } else { + this.controls.panel.classList.remove(this.ClassNames.show); + this.controls.panel.classList.add(this.ClassNames.hidden); + this.showShowControlsButton(true); + } + }, + + isPlayable: function() + { + return this.video.readyState > HTMLMediaElement.HAVE_NOTHING && !this.video.error; + }, + + updateStatusDisplay: function(event) + { + this.updateShouldListenForPlaybackTargetAvailabilityEvent(); + if (this.video.error !== null) + this.controls.statusDisplay.innerText = this.UIString('Error'); + else if (this.isLive && this.video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) + this.controls.statusDisplay.innerText = this.UIString('Live Broadcast'); + else if (!this.isPlayable() && this.video.networkState === HTMLMediaElement.NETWORK_LOADING) + this.controls.statusDisplay.innerText = this.UIString('Loading'); + else + this.controls.statusDisplay.innerText = ''; + + this.setStatusHidden(!this.isLive && this.isPlayable()); + }, + + handleLoadStart: function(event) + { + this.updateStatusDisplay(); + this.updateProgress(); + }, + + handleError: function(event) + { + this.updateStatusDisplay(); + }, + + handleAbort: function(event) + { + this.updateStatusDisplay(); + }, + + handleSuspend: function(event) + { + this.updateStatusDisplay(); + }, + + handleStalled: function(event) + { + this.updateStatusDisplay(); + this.updateProgress(); + }, + + handleWaiting: function(event) + { + this.updateStatusDisplay(); + }, + + handleReadyStateChange: function(event) + { + this.updateReadyState(); + this.updateDuration(); + this.updateCaptionButton(); + this.updateCaptionContainer(); + this.updateFullscreenButtons(); + this.updateWirelessTargetAvailable(); + this.updateWirelessTargetPickerButton(); + this.updateProgress(); + this.updateControls(); + }, + + handleTimeUpdate: function(event) + { + if (!this.scrubbing) { + this.updateTime(); + this.updateProgress(); + } + this.drawTimelineBackground(); + }, + + handleDurationChange: function(event) + { + this.updateDuration(); + this.updateTime(); + this.updateProgress(); + }, + + handlePlay: function(event) + { + this.setPlaying(true); + }, + + handlePause: function(event) + { + this.setPlaying(false); + }, + + handleProgress: function(event) + { + this.updateProgress(); + }, + + handleVolumeChange: function(event) + { + this.updateVolume(); + }, + + handleTextTrackChange: function(event) + { + this.updateCaptionContainer(); + }, + + handleTextTrackAdd: function(event) + { + var track = event.track; + + if (this.trackHasThumbnails(track) && track.mode === 'disabled') + track.mode = 'hidden'; + + this.updateThumbnail(); + this.updateCaptionButton(); + this.updateCaptionContainer(); + }, + + handleTextTrackRemove: function(event) + { + this.updateThumbnail(); + this.updateCaptionButton(); + this.updateCaptionContainer(); + }, + + handleAudioTrackChange: function(event) + { + this.updateHasAudio(); + }, + + handleAudioTrackAdd: function(event) + { + this.updateHasAudio(); + this.updateCaptionButton(); + }, + + handleAudioTrackRemove: function(event) + { + this.updateHasAudio(); + this.updateCaptionButton(); + }, + + presentationMode: function() { + if ('webkitPresentationMode' in this.video) + return this.video.webkitPresentationMode; + + if (this.isFullScreen()) + return 'fullscreen'; + + return 'inline'; + }, + + isFullScreen: function() + { + if (!this.video.webkitDisplayingFullscreen) + return false; + + if ('webkitPresentationMode' in this.video && this.video.webkitPresentationMode === 'picture-in-picture') + return false; + + return true; + }, + + updatePictureInPictureButton: function() + { + var shouldShowPictureInPictureButton = (Controller.gSimulatePictureInPictureAvailable || ('webkitSupportsPresentationMode' in this.video && this.video.webkitSupportsPresentationMode('picture-in-picture'))) && this.hasVideo(); + if (shouldShowPictureInPictureButton) { + if (!this.controls.pictureInPictureButton.parentElement) { + if (this.controls.fullscreenButton.parentElement == this.controls.panel) + this.controls.panel.insertBefore(this.controls.pictureInPictureButton, this.controls.fullscreenButton); + else + this.controls.panel.appendChild(this.controls.pictureInPictureButton); + } + this.controls.pictureInPictureButton.classList.remove(this.ClassNames.hidden); + } else + this.controls.pictureInPictureButton.classList.add(this.ClassNames.hidden); + }, + + timelineStepFromVideoDuration: function() + { + var step; + var duration = this.video.duration; + if (duration <= 10) + step = .5; + else if (duration <= 60) + step = 1; + else if (duration <= 600) + step = 10; + else if (duration <= 3600) + step = 30; + else + step = 60; + + return step; + }, + + incrementTimelineValue: function() + { + var value = this.video.currentTime + this.timelineStepFromVideoDuration(); + return value > this.video.duration ? this.video.duration : value; + }, + + decrementTimelineValue: function() + { + var value = this.video.currentTime - this.timelineStepFromVideoDuration(); + return value < 0 ? 0 : value; + }, + + showInlinePlaybackPlaceholderWhenSafe: function() { + if (this.presentationMode() != 'picture-in-picture') + return; + + if (!this.host.isVideoLayerInline) { + this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.hidden); + this.base.classList.add(this.ClassNames.placeholderShowing); + } else + setTimeout(this.showInlinePlaybackPlaceholderWhenSafe.bind(this), this.PlaceholderPollingDelay); + }, + + shouldReturnVideoLayerToInline: function() + { + var presentationMode = this.presentationMode(); + return presentationMode === 'inline' || presentationMode === 'fullscreen'; + }, + + updatePictureInPicturePlaceholder: function() + { + var presentationMode = this.presentationMode(); + + switch (presentationMode) { + case 'inline': + this.controls.panel.classList.remove(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden); + this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.pictureInPicture); + this.base.classList.remove(this.ClassNames.placeholderShowing); + + this.controls.pictureInPictureButton.classList.remove(this.ClassNames.returnFromPictureInPicture); + break; + case 'picture-in-picture': + this.controls.panel.classList.add(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.pictureInPicture); + this.showInlinePlaybackPlaceholderWhenSafe(); + + this.controls.inlinePlaybackPlaceholderTextTop.innerText = this.UIString('This video is playing in Picture in Picture'); + this.controls.inlinePlaybackPlaceholderTextTop.classList.add(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholderTextBottom.innerText = ""; + this.controls.inlinePlaybackPlaceholderTextBottom.classList.add(this.ClassNames.pictureInPicture); + + this.controls.pictureInPictureButton.classList.add(this.ClassNames.returnFromPictureInPicture); + break; + default: + this.controls.panel.classList.remove(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.pictureInPicture); + this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.pictureInPicture); + + this.controls.pictureInPictureButton.classList.remove(this.ClassNames.returnFromPictureInPicture); + break; + } + }, + + handlePresentationModeChange: function(event) + { + this.updatePictureInPicturePlaceholder(); + this.updateControls(); + this.updateCaptionContainer(); + this.resetHideControlsTimer(); + if (this.presentationMode() != 'fullscreen' && this.video.paused && this.controlsAreHidden()) + this.showControls(); + this.host.setPreparedToReturnVideoLayerToInline(this.shouldReturnVideoLayerToInline()); + }, + + handleFullscreenChange: function(event) + { + this.updateBase(); + this.updateControls(); + this.updateFullscreenButtons(); + this.updateWirelessPlaybackStatus(); + + if (this.isFullScreen()) { + this.controls.fullscreenButton.classList.add(this.ClassNames.exit); + this.controls.fullscreenButton.setAttribute('aria-label', this.UIString('Exit Full Screen')); + this.host.enteredFullscreen(); + } else { + this.controls.fullscreenButton.classList.remove(this.ClassNames.exit); + this.controls.fullscreenButton.setAttribute('aria-label', this.UIString('Display Full Screen')); + this.host.exitedFullscreen(); + } + + if ('webkitPresentationMode' in this.video) + this.handlePresentationModeChange(event); + }, + + handleShowControlsClick: function(event) + { + if (!this.video.controls && !this.isFullScreen()) + return; + + if (this.controlsAreHidden()) + this.showControls(true); + }, + + handleWrapperMouseMove: function(event) + { + if (!this.video.controls && !this.isFullScreen()) + return; + + if (this.controlsAreHidden()) + this.showControls(); + this.resetHideControlsTimer(); + + if (!this.isDragging) + return; + var delta = new WebKitPoint(event.clientX - this.initialDragLocation.x, event.clientY - this.initialDragLocation.y); + this.controls.panel.style.left = this.initialOffset.x + delta.x + 'px'; + this.controls.panel.style.top = this.initialOffset.y + delta.y + 'px'; + event.stopPropagation() + }, + + handleWrapperMouseOut: function(event) + { + this.hideControls(); + this.clearHideControlsTimer(); + }, + + handleWrapperMouseUp: function(event) + { + this.isDragging = false; + this.stopListeningFor(this.base, 'mouseup', 'handleWrapperMouseUp', true); + }, + + handlePanelMouseDown: function(event) + { + if (event.target != this.controls.panelTint && event.target != this.controls.inlinePlaybackPlaceholder) + return; + + if (!this.isFullScreen()) + return; + + this.listenFor(this.base, 'mouseup', this.handleWrapperMouseUp, true); + this.isDragging = true; + this.initialDragLocation = new WebKitPoint(event.clientX, event.clientY); + this.initialOffset = new WebKitPoint( + parseInt(this.controls.panel.style.left) | 0, + parseInt(this.controls.panel.style.top) | 0 + ); + }, + + handlePanelTransitionEnd: function(event) + { + var opacity = window.getComputedStyle(this.controls.panel).opacity; + if (!parseInt(opacity) && !this.controlsAlwaysVisible() && (this.video.controls || this.isFullScreen())) { + this.base.removeChild(this.controls.inlinePlaybackPlaceholder); + this.base.removeChild(this.controls.panel); + } + }, + + handlePanelClick: function(event) + { + // Prevent clicks in the panel from playing or pausing the video in a MediaDocument. + event.preventDefault(); + }, + + handlePanelDragStart: function(event) + { + // Prevent drags in the panel from triggering a drag event on the