summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/mediacontrols
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/Modules/mediacontrols')
-rw-r--r--Source/WebCore/Modules/mediacontrols/MediaControlsHost.cpp294
-rw-r--r--Source/WebCore/Modules/mediacontrols/MediaControlsHost.h101
-rw-r--r--Source/WebCore/Modules/mediacontrols/MediaControlsHost.idl65
-rw-r--r--Source/WebCore/Modules/mediacontrols/assets-apple-iOS.svg52
-rw-r--r--Source/WebCore/Modules/mediacontrols/mediaControlsApple.css1153
-rw-r--r--Source/WebCore/Modules/mediacontrols/mediaControlsApple.js2509
-rw-r--r--Source/WebCore/Modules/mediacontrols/mediaControlsBase.css758
-rw-r--r--Source/WebCore/Modules/mediacontrols/mediaControlsBase.js1336
-rw-r--r--Source/WebCore/Modules/mediacontrols/mediaControlsGtk.js258
-rw-r--r--Source/WebCore/Modules/mediacontrols/mediaControlsiOS.css733
-rw-r--r--Source/WebCore/Modules/mediacontrols/mediaControlsiOS.js628
11 files changed, 7887 insertions, 0 deletions
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 <runtime/JSCJSValueInlines.h>
+
+namespace WebCore {
+
+const AtomicString& MediaControlsHost::automaticKeyword()
+{
+ static NeverDestroyed<const AtomicString> automatic("automatic", AtomicString::ConstructFromLiteral);
+ return automatic;
+}
+
+const AtomicString& MediaControlsHost::forcedOnlyKeyword()
+{
+ static NeverDestroyed<const AtomicString> forcedOn("forced-only", AtomicString::ConstructFromLiteral);
+ return forcedOn;
+}
+
+const AtomicString& MediaControlsHost::alwaysOnKeyword()
+{
+ static NeverDestroyed<const AtomicString> alwaysOn("always-on", AtomicString::ConstructFromLiteral);
+ return alwaysOn;
+}
+
+const AtomicString& MediaControlsHost::manualKeyword()
+{
+ static NeverDestroyed<const AtomicString> alwaysOn("manual", AtomicString::ConstructFromLiteral);
+ return alwaysOn;
+}
+
+
+Ref<MediaControlsHost> MediaControlsHost::create(HTMLMediaElement* mediaElement)
+{
+ return adoptRef(*new MediaControlsHost(mediaElement));
+}
+
+MediaControlsHost::MediaControlsHost(HTMLMediaElement* mediaElement)
+ : m_mediaElement(mediaElement)
+{
+ ASSERT(mediaElement);
+}
+
+MediaControlsHost::~MediaControlsHost()
+{
+}
+
+Vector<RefPtr<TextTrack>> MediaControlsHost::sortedTrackListForMenu(TextTrackList& trackList)
+{
+ Page* page = m_mediaElement->document().page();
+ if (!page)
+ return { };
+
+ return page->group().captionPreferences().sortedTrackListForMenu(&trackList);
+}
+
+Vector<RefPtr<AudioTrack>> 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<TextOrAudioTrack>& 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 <bindings/ScriptObject.h>
+#include <wtf/RefCounted.h>
+#include <wtf/Variant.h>
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class AudioTrack;
+class AudioTrackList;
+class Element;
+class HTMLMediaElement;
+class MediaControlTextTrackContainerElement;
+class TextTrack;
+class TextTrackList;
+
+class MediaControlsHost : public RefCounted<MediaControlsHost> {
+public:
+ static Ref<MediaControlsHost> create(HTMLMediaElement*);
+ ~MediaControlsHost();
+
+ static const AtomicString& automaticKeyword();
+ static const AtomicString& forcedOnlyKeyword();
+ static const AtomicString& alwaysOnKeyword();
+ static const AtomicString& manualKeyword();
+
+ Vector<RefPtr<TextTrack>> sortedTrackListForMenu(TextTrackList&);
+ Vector<RefPtr<AudioTrack>> sortedTrackListForMenu(AudioTrackList&);
+
+ using TextOrAudioTrack = WTF::Variant<RefPtr<TextTrack>, RefPtr<AudioTrack>>;
+ String displayNameForTrack(const std::optional<TextOrAudioTrack>&);
+
+ 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<MediaControlTextTrackContainerElement> 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<TextTrack> sortedTrackListForMenu(TextTrackList trackList);
+ sequence<AudioTrack> 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 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 190 44">
+
+ <!--
+ NOTE: THIS FILE IS NOT PART OF THE BUILD PROCESS. EDITS
+ HERE ARE NOT AUTOMATICALLY COPIED TO THE CSS FILES.
+
+ This file holds the raw assets used in mediaControlsApple.css.
+ This is not part of the build process at the moment, but manually
+ copied into the CSS. However, editing from CSS is a burden, so
+ the idea is that you develop here and then paste into CSS.
+ -->
+
+ <!-- PLAY -->
+
+ <g transform="translate(0 0)">
+ <rect width="44" height="44" fill="rgb(255, 255, 200)"/>
+ <svg width="44" height="44">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44">
+ <path fill="rgba(0, 0, 0, 0.8)" d="M10,11 32,22 10,33z"/>
+ </svg>
+ </svg>
+ </g>
+
+ <!-- PAUSE -->
+
+ <g transform="translate(46 0)">
+ <rect width="44" height="44" fill="rgb(255, 255, 200)"/>
+ <svg width="44" height="44">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44">
+ <g fill="rgba(0, 0, 0, 0.8)">
+ <rect x="13" y="11" width="6" height="22"/>
+ <rect x="24" y="11" width="6" height="22"/>
+ </g>
+ </svg>
+ </svg>
+ </g>
+
+ <!-- FULLSCREEN -->
+
+ <g transform="translate(92 0)">
+ <rect width="44" height="44" fill="rgb(255, 255, 200)"/>
+ <svg width="44" height="44">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44">
+ <g stroke="rgba(0, 0, 0, 0.8)" fill="none">
+ <path d="M 14,20 v -6 h 6 M 14,14 l 6,6"/>
+ <path d="M 30,24 v 6 h -6 M 30,30 l -6,-6"/>
+ </g>
+ </svg>
+ </svg>
+ </g>
+
+</svg> \ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 17" fill="rgba(255,255,255,0.45)"><path d="m 7.9131,2 0,-1.548 -2.586,2.155 0,-2.171 -2.582,2.208 2.582,2.175 0,-2.139 2.586,2.155 0,-1.276 c 3.138,0.129 5.491,2.681 5.543,5.838 l -1.031,0 0.016,0.215 1.015,0 c -0.06,3.19 -2.629,5.765 -5.819,5.833 l 0,-1.018 -0.214,0 0,1.021 c -3.21,-0.047 -5.801,-2.631 -5.862,-5.836 l 1.045,0 -0.016,-0.215 -1.045,0 c -0.052,-0.288 -0.318,-0.654 -0.766,-0.654 -0.538,0 -0.755,0.484 -0.755,0.75 0,4.146 3.331,7.506 7.476,7.506 4.146,0 7.506,-3.36 7.506,-7.506 0,-4.059 -3.066,-7.357 -7.093,-7.493"/><path d="m 5.1729,11.0518 c -0.38,0 -0.668,-0.129 -0.945,-0.366 -0.083,-0.071 -0.186,-0.134 -0.338,-0.134 -0.277,0 -0.511,0.238 -0.511,0.521 0,0.154 0.083,0.301 0.179,0.383 0.394,0.346 0.911,0.563 1.601,0.563 1.077,0 1.739,-0.451 1.739,-1.608 l 0,-0.013 c 0,-0.911 -0.641,-1.265 -1.296,-1.376 l 0.945,-0.919 c 0.193,-0.19 0.317,-0.337 0.317,-0.604 0,-0.294 -0.228,-0.477 -0.538,-0.477 l -2.354,0 c -0.248,0 -0.455,0.21 -0.455,0.464 0,0.253 0.207,0.463 0.455,0.463 l 1.485,0 -0.939,0.961 c -0.166,0.169 -0.228,0.295 -0.228,0.444 0,0.25 0.207,0.463 0.455,0.463 l 0.166,0 c 0.594,0 0.945,0.222 0.945,0.624 l 0,0.012 c 0,0.367 -0.282,0.599 -0.453,0.599"/><path d="m 10.354,9.5342 c 0,0.876 -0.379,1.525 -0.979,1.525 -0.599,0 -0.992,-0.655 -0.992,-1.539 l 0,-0.012 c 0,-0.884 0.388,-1.527 0.979,-1.527 0.592,0 0.992,0.663 0.992,1.539 l 0,0.014 z m -0.979,-2.512 c -1.197,0 -2.008,1.097 -2.008,2.498 l 0,0.014 c 0,1.401 0.792,2.484 1.995,2.484 1.205,0 2.01,-1.097 2.01,-2.498 l 0,-0.012 c 0,-1.402 -0.805,-2.486 -1.997,-2.486"/></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 15" fill="rgba(255,255,255,0.45)"><rect x="0" y="1" width="4" height="13"/><rect x="8" y="1" width="4" height="13"/></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 15" fill="rgba(255,255,255,0.45)"><path d="M 0,1 12,7.5 0,14 z"/></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="rgba(255,255,255,0.45)" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="rgba(255,255,255,0.45)" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="rgba(255,255,255,0.45)" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/><path d="M 9,5.5 C 10.75,7 10.75,9 9,10.5"/><path d="M 10,3.5 C 13,4.5 13,11.5 10,12.5" /><path d="M 11,1.5 C 15.67,3.5 15.67,12.5 11,14.5"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="rgba(255,255,255,0.45)" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="rgba(255,255,255,0.45)" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="rgba(255,255,255,0.45)" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 15" stroke="rgba(255,255,255,0.45)" fill="none"><defs> <clipPath id="cut-hole"><rect x="0" y="1" width="16" height="10"/><rect x="0" y="11" width="9" height="1"/><rect x="11" y="11" width="5" height="1"/></clipPath></defs> <rect x="0.5" y="1.5" rx="1" ry="1" width="15" height="10" clip-path="url(#cut-hole)"/><path d="M 2,6.5 h 4"/><path d="M 7,6.5 h 7"/><path d="M 3,8.5 h 2"/><path d="M 6,8.5 h 3"/><path d="M 10,8.5 h 3"/><path d="M 8.5,11.5 L 8.5,13.75 L 11,11"/></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><polygon fill="rgb(163, 163, 163)" points="252.301,4.477 126.667,194.104 43.358,108.3 6.868,161.408 132.515,290.814 297.732,49.926"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><polygon fill="rgba(255,255,255,0.45)" points="252.301,4.477 126.667,194.104 43.358,108.3 6.868,161.408 132.515,290.814 297.732,49.926"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 12" fill="rgba(255,255,255,0.45)"><polygon points="6 8 1 8 1 1 14 1 14 6 15 6 15 0 0 0 0 9 6 9 6 8"/><rect x="7" y="7" width="9" height="5"/><polyline transform="translate(4, 4) rotate(180) translate(-4, -4)" points="5.33 2 5.33 3 3.67 3 5.67 5 5 5.67 3 3.67 3 5.33 2 5.33 2 2"/></svg>');
+ }
+
+video::-webkit-media-controls-panel .picture-in-picture-button.return-from-picture-in-picture {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 12" fill="rgba(255,255,255,0.45)"><polygon points="6 8 1 8 1 1 14 1 14 6 15 6 15 0 0 0 0 9 6 9 6 8"/><rect x="7" y="7" width="9" height="5"/><polyline points="5.33 2 5.33 3 3.67 3 5.67 5 5 5.67 3 3.67 3 5.33 2 5.33 2 2"/></svg>');
+ }
+
+video::-webkit-media-controls-fullscreen-button,
+audio::-webkit-media-controls-fullscreen-button {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" stroke="rgba(255,255,255,0.45)" fill="none"><path d="M 7,1.5 L 12.5,1.5 L 12.5,7"/><path d="M 0.5,8 L 0.5,13.5 L 6,13.5"/><path stroke-linecap="round" d="M 12.5,1.5 L 7.5,6.5"/><path stroke-linecap="round" d="M 0.5,13.5 L 5.5,8.5"/></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" stroke="rgba(255,255,255,0.45)" fill="none"><path d="M 7.5,1 L 7.5,6.5 L 13,6.5"/><path d="M 0,8.5 L 5.5,8.5 L 5.5,14"/><path stroke-linecap="round" d="M 7.5,6.5 L 12.5,1.5"/><path stroke-linecap="round" d="M 5.5,8.5 L 0.5,13.5"/></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="rgba(255,255,255,0.45)" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="rgba(255,255,255,0.45)" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="rgba(255,255,255,0.45)" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/><path d="M 9,5.5 C 10.75,7 10.75,9 9,10.5"/><path d="M 10,3.5 C 13,4.5 13,11.5 10,12.5" /><path d="M 11,1.5 C 15.67,3.5 15.67,12.5 11,14.5"/></svg>');
+}
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="rgba(255,255,255,0.45)" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="rgba(255,255,255,0.45)" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="rgba(255,255,255,0.45)" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/><path d="M 9,5.5 C 10.75,7 10.75,9 9,10.5"/></svg>');
+}
+
+video:-webkit-full-screen::-webkit-media-controls-play-button {
+ position: absolute;
+
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 23" fill="rgba(255,255,255,0.45)"><path d="M 0,0 0,22 8,22 8,0 z"/><path d="M 13,0 13,22 21,22 21,0 z"/></svg>');
+
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 23" fill="rgba(255,255,255,0.45)"><path d="M 0,0 21,11.5 0,23 z"/></svg>');
+}
+
+video:-webkit-full-screen::-webkit-media-controls-seek-back-button {
+ position: absolute;
+
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 15" fill="rgba(255,255,255,0.45)"><path d="M 24,0 12,7 24,15 z"/><path d="M 12,0 0,7 12,15 z"/></svg>');
+
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 15" fill="rgba(255,255,255,0.45)"><path d="M 0,0 12,7 0,15 z"/><path d="M 12,0 24,7 12,15 z"/></svg>');
+
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="white" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="white" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="white" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/><path d="M 9,5.5 C 10.75,7 10.75,9 9,10.5"/><path d="M 10,3.5 C 13,4.5 13,11.5 10,12.5" /><path d="M 11,1.5 C 15.67,3.5 15.67,12.5 11,14.5"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="white" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="white" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="white" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 15" stroke="white" fill="none"><defs> <clipPath id="cut-hole"><rect x="0" y="1" width="16" height="10"/><rect x="0" y="11" width="9" height="1"/><rect x="11" y="11" width="5" height="1"/></clipPath></defs> <rect x="0.5" y="1.5" rx="1" ry="1" width="15" height="10" clip-path="url(#cut-hole)"/><path d="M 2,6.5 h 4"/><path d="M 7,6.5 h 7"/><path d="M 3,8.5 h 2"/><path d="M 6,8.5 h 3"/><path d="M 10,8.5 h 3"/><path d="M 8.5,11.5 L 8.5,13.75 L 11,11"/></svg>');
+}
+
+video::-webkit-media-controls-rewind-button:active,
+audio::-webkit-media-controls-rewind-button:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 17" fill="white"><path d="m 7.9131,2 0,-1.548 -2.586,2.155 0,-2.171 -2.582,2.208 2.582,2.175 0,-2.139 2.586,2.155 0,-1.276 c 3.138,0.129 5.491,2.681 5.543,5.838 l -1.031,0 0.016,0.215 1.015,0 c -0.06,3.19 -2.629,5.765 -5.819,5.833 l 0,-1.018 -0.214,0 0,1.021 c -3.21,-0.047 -5.801,-2.631 -5.862,-5.836 l 1.045,0 -0.016,-0.215 -1.045,0 c -0.052,-0.288 -0.318,-0.654 -0.766,-0.654 -0.538,0 -0.755,0.484 -0.755,0.75 0,4.146 3.331,7.506 7.476,7.506 4.146,0 7.506,-3.36 7.506,-7.506 0,-4.059 -3.066,-7.357 -7.093,-7.493"/><path d="m 5.1729,11.0518 c -0.38,0 -0.668,-0.129 -0.945,-0.366 -0.083,-0.071 -0.186,-0.134 -0.338,-0.134 -0.277,0 -0.511,0.238 -0.511,0.521 0,0.154 0.083,0.301 0.179,0.383 0.394,0.346 0.911,0.563 1.601,0.563 1.077,0 1.739,-0.451 1.739,-1.608 l 0,-0.013 c 0,-0.911 -0.641,-1.265 -1.296,-1.376 l 0.945,-0.919 c 0.193,-0.19 0.317,-0.337 0.317,-0.604 0,-0.294 -0.228,-0.477 -0.538,-0.477 l -2.354,0 c -0.248,0 -0.455,0.21 -0.455,0.464 0,0.253 0.207,0.463 0.455,0.463 l 1.485,0 -0.939,0.961 c -0.166,0.169 -0.228,0.295 -0.228,0.444 0,0.25 0.207,0.463 0.455,0.463 l 0.166,0 c 0.594,0 0.945,0.222 0.945,0.624 l 0,0.012 c 0,0.367 -0.282,0.599 -0.453,0.599"/><path d="m 10.354,9.5342 c 0,0.876 -0.379,1.525 -0.979,1.525 -0.599,0 -0.992,-0.655 -0.992,-1.539 l 0,-0.012 c 0,-0.884 0.388,-1.527 0.979,-1.527 0.592,0 0.992,0.663 0.992,1.539 l 0,0.014 z m -0.979,-2.512 c -1.197,0 -2.008,1.097 -2.008,2.498 l 0,0.014 c 0,1.401 0.792,2.484 1.995,2.484 1.205,0 2.01,-1.097 2.01,-2.498 l 0,-0.012 c 0,-1.402 -0.805,-2.486 -1.997,-2.486"/></svg>');
+}
+
+video::-webkit-media-controls-panel button.paused:active,
+audio::-webkit-media-controls-panel button.paused:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 15" fill="white"><path d="M 0,1 12,7.5 0,14 z"/></svg>');
+}
+
+video::-webkit-media-controls-play-button:active,
+audio::-webkit-media-controls-play-button:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 15" fill="white"><rect x="0" y="1" width="4" height="13"/><rect x="8" y="1" width="4" height="13"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="white" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="white" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="white" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/><path d="M 9,5.5 C 10.75,7 10.75,9 9,10.5"/><path d="M 10,3.5 C 13,4.5 13,11.5 10,12.5" /><path d="M 11,1.5 C 15.67,3.5 15.67,12.5 11,14.5"/></svg>');
+}
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" stroke="white" fill="none" stroke-width="1.25" stroke-linecap="round"><defs><clipPath id="cut-hole"><rect x="3" y="0" width="3.5" height="15"/></clipPath></defs><path stroke="none" fill="white" shape-rendering="crispEdges" d="M 0,10 0,6 3,6 3,10 z"/><path stroke="none" fill="white" clip-path="url(#cut-hole)" shape-rendering="crispEdges" d="M 3.5,6 6.5,3 6.5,13 3.5,10 z"/><path d="M 9,5.5 C 10.75,7 10.75,9 9,10.5"/></svg>');
+}
+
+video:-webkit-full-screen::-webkit-media-controls-play-button:active{
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 23" fill="white"><path d="M 0,0 0,22 8,22 8,0 z"/><path d="M 13,0 13,22 21,22 21,0 z"/></svg>');
+}
+
+video:-webkit-full-screen::-webkit-media-controls-panel button.paused:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 23" fill="white"><path d="M 0,0 21,11.5 0,23 z"/></svg>');
+}
+
+video:-webkit-full-screen::-webkit-media-controls-seek-back-button:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 15" fill="white"><path d="M 24,0 12,7 24,15 z"/><path d="M 12,0 0,7 12,15 z"/></svg>');
+}
+
+video:-webkit-full-screen::-webkit-media-controls-seek-forward-button:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 15" fill="white"><path d="M 0,0 12,7 0,15 z"/><path d="M 12,0 24,7 12,15 z"/></svg>');
+}
+
+video::-webkit-media-controls-fullscreen-button:active,
+audio::-webkit-media-controls-fullscreen-button:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" stroke="white" fill="none"><path d="M 7,1.5 L 12.5,1.5 L 12.5,7"/><path d="M 0.5,8 L 0.5,13.5 L 6,13.5"/><path stroke-linecap="round" d="M 12.5,1.5 L 7.5,6.5"/><path stroke-linecap="round" d="M 0.5,13.5 L 5.5,8.5"/></svg>');
+}
+
+video::-webkit-media-controls-panel button.exit:active,
+audio::-webkit-media-controls-panel button.exit:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" stroke="white" fill="none"><path d="M 7.5,1 L 7.5,6.5 L 13,6.5"/><path d="M 0,8.5 L 5.5,8.5 L 5.5,14"/><path stroke-linecap="round" d="M 7.5,6.5 L 12.5,1.5"/><path stroke-linecap="round" d="M 5.5,8.5 L 0.5,13.5"/></svg>');
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 15" stroke="white"><defs> <clipPath fill-rule="evenodd" id="cut-hole"><path d="M 0,0.5 L 16,0.5 L 16,15.5 L 0,15.5 z M 0,14.5 L 16,14.5 L 8,5 z"/></clipPath></defs><rect fill="none" clip-path="url(#cut-hole)" x="0.5" y="2" width="15" height="8"/><path stroke="none" fill="white" d="M 3.5,13.25 L 12.5,13.25 L 8,8 z"/></svg>');
+}
+
+video::-webkit-media-controls-panel .picture-in-picture-button:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 12" fill="white"><polygon points="6 8 1 8 1 1 14 1 14 6 15 6 15 0 0 0 0 9 6 9 6 8"/><rect x="7" y="7" width="9" height="5"/><path d="M5.5,4.45a0.5,0.5,0,0,0-.5.5v1.3L2.58,3.83a0.5,0.5,0,0,0-.71.71L4.33,7H3A0.5,0.5,0,0,0,3,8H5.5A0.5,0.5,0,0,0,6,7.5V4.95A0.5,0.5,0,0,0,5.5,4.45Z" transform="translate(0 -2)"/></svg>');
+}
+
+video::-webkit-media-controls-panel .picture-in-picture-button.return-from-picture-in-picture:active {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 12" fill="white"><polygon points="6 8 1 8 1 1 14 1 14 6 15 6 15 0 0 0 0 9 6 9 6 8"/><rect x="7" y="7" width="9" height="5"/><path d="M2.22,7.23a0.5,0.5,0,0,0,.5-0.5V5.43L5.15,7.85a0.5,0.5,0,0,0,.71-0.71L3.39,4.68H4.77a0.5,0.5,0,0,0,0-1H2.22a0.5,0.5,0,0,0-.5.5V6.73A0.5,0.5,0,0,0,2.22,7.23Z" transform="translate(0 -2)"/></svg>');
+}
+
+/* ==================== 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 131 90"><g fill="none" stroke="-apple-system-gray" stroke-width="4"><rect x="2" y="2" width="127" height="76"/><line x1="30" y1="88" x2="101" y2="88"/></g></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 62 43"><g fill="none" stroke="-apple-system-gray" stroke-width="2"><rect x="1" y="1" width="60" height="36"/><line x1="14" y1="42" x2="48" y2="42"/></g></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 304 150"><g fill="none" stroke="-apple-system-gray" stroke-width="3"><path d="m 172,106 -81,0 c -3.5,0 -6,-2.5 -6,-6 l 0,-89 c 0,-3.5 2.5,-6 6,-6 l 122,0 c 3.5,0 6,2.5 6,6 l 0,57" /><path d="m 233,119 -53,0 c -3,0 -3,-0 -3,-3 l 0,-40 c 0,-3 0,-3 3,-3 l 53,0 c 3,0 3,0 3,3 l 0,40 c 0,3 0,3 -3,3 z" /></g></svg>');
+}
+
+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 <video> element.
+ event.preventDefault();
+ },
+
+ handlePlaceholderClick: function(event)
+ {
+ // Prevent clicks in the placeholder from playing or pausing the video in a MediaDocument.
+ event.preventDefault();
+ },
+
+ handleRewindButtonClicked: function(event)
+ {
+ var newTime = Math.max(
+ this.video.currentTime - this.RewindAmount,
+ this.video.seekable.start(0));
+ this.video.currentTime = newTime;
+ return true;
+ },
+
+ canPlay: function()
+ {
+ return this.video.paused || this.video.ended || this.video.readyState < HTMLMediaElement.HAVE_METADATA;
+ },
+
+ handlePlayButtonClicked: function(event)
+ {
+ if (this.canPlay()) {
+ this.canToggleShowControlsButton = true;
+ this.video.play();
+ } else
+ this.video.pause();
+ return true;
+ },
+
+ handleTimelineInput: function(event)
+ {
+ if (this.scrubbing)
+ this.video.pause();
+
+ this.video.fastSeek(this.controls.timeline.value);
+ this.updateControlsWhileScrubbing();
+ },
+
+ handleTimelineChange: function(event)
+ {
+ this.video.currentTime = this.controls.timeline.value;
+ this.updateProgress();
+ },
+
+ handleTimelineDown: function(event)
+ {
+ this.controls.thumbnail.classList.add(this.ClassNames.show);
+ },
+
+ handleTimelineUp: function(event)
+ {
+ this.controls.thumbnail.classList.remove(this.ClassNames.show);
+ },
+
+ handleTimelineMouseOver: function(event)
+ {
+ this.controls.thumbnail.classList.add(this.ClassNames.show);
+ },
+
+ handleTimelineMouseOut: function(event)
+ {
+ this.controls.thumbnail.classList.remove(this.ClassNames.show);
+ },
+
+ handleTimelineMouseMove: function(event)
+ {
+ if (this.controls.thumbnail.classList.contains(this.ClassNames.hidden))
+ return;
+
+ this.updateTimelineMetricsIfNeeded();
+ this.controls.thumbnail.classList.add(this.ClassNames.show);
+ var localPoint = webkitConvertPointFromPageToNode(this.controls.timeline, new WebKitPoint(event.clientX, event.clientY));
+ var percent = (localPoint.x - this.timelineLeft) / this.timelineWidth;
+ percent = Math.max(Math.min(1, percent), 0);
+ this.controls.thumbnail.style.left = percent * 100 + '%';
+
+ var thumbnailTime = percent * this.video.duration;
+ for (var i = 0; i < this.video.textTracks.length; ++i) {
+ var track = this.video.textTracks[i];
+ if (!this.trackHasThumbnails(track))
+ continue;
+
+ if (!track.cues)
+ continue;
+
+ for (var j = 0; j < track.cues.length; ++j) {
+ var cue = track.cues[j];
+ if (thumbnailTime >= cue.startTime && thumbnailTime < cue.endTime) {
+ this.controls.thumbnailImage.src = cue.text;
+ return;
+ }
+ }
+ }
+ },
+
+ handleTimelineMouseDown: function(event)
+ {
+ this.scrubbing = true;
+ },
+
+ handleTimelineMouseUp: function(event)
+ {
+ this.scrubbing = false;
+ },
+
+ handleTimelineKeyDown: function(event)
+ {
+ if (event.keyCode == this.KeyCodes.left)
+ this.controls.timeline.value = this.decrementTimelineValue();
+ else if (event.keyCode == this.KeyCodes.right)
+ this.controls.timeline.value = this.incrementTimelineValue();
+ },
+
+ handleMuteButtonClicked: function(event)
+ {
+ this.video.muted = !this.video.muted;
+ if (this.video.muted)
+ this.controls.muteButton.setAttribute('aria-checked', 'true');
+ else
+ this.controls.muteButton.setAttribute('aria-checked', 'false');
+ this.drawVolumeBackground();
+ return true;
+ },
+
+ handleMuteBoxOver: function(event)
+ {
+ this.drawVolumeBackground();
+ },
+
+ handleMinButtonClicked: function(event)
+ {
+ if (this.video.muted) {
+ this.video.muted = false;
+ this.controls.muteButton.setAttribute('aria-checked', 'false');
+ }
+ this.video.volume = 0;
+ return true;
+ },
+
+ handleMaxButtonClicked: function(event)
+ {
+ if (this.video.muted) {
+ this.video.muted = false;
+ this.controls.muteButton.setAttribute('aria-checked', 'false');
+ }
+ this.video.volume = 1;
+ },
+
+ updateVideoVolume: function()
+ {
+ if (this.video.muted) {
+ this.video.muted = false;
+ this.controls.muteButton.setAttribute('aria-checked', 'false');
+ }
+ this.video.volume = this.controls.volume.value;
+ this.controls.volume.setAttribute('aria-valuetext', `${parseInt(this.controls.volume.value * 100)}%`);
+ },
+
+ handleVolumeSliderInput: function(event)
+ {
+ this.updateVideoVolume();
+ this.drawVolumeBackground();
+ },
+
+ handleVolumeSliderChange: function(event)
+ {
+ this.updateVideoVolume();
+ },
+
+ handleVolumeSliderMouseDown: function(event)
+ {
+ this.isVolumeSliderActive = true;
+ this.drawVolumeBackground();
+ },
+
+ handleVolumeSliderMouseUp: function(event)
+ {
+ this.isVolumeSliderActive = false;
+ this.drawVolumeBackground();
+ },
+
+ handleCaptionButtonClicked: function(event)
+ {
+ if (this.captionMenu)
+ this.destroyCaptionMenu();
+ else
+ this.buildCaptionMenu();
+ return true;
+ },
+
+ hasVideo: function()
+ {
+ return this.video.videoTracks && this.video.videoTracks.length;
+ },
+
+ updateFullscreenButtons: function()
+ {
+ var shouldBeHidden = !this.video.webkitSupportsFullscreen || !this.hasVideo();
+ this.controls.fullscreenButton.classList.toggle(this.ClassNames.hidden, shouldBeHidden && !this.isFullScreen());
+ this.updatePictureInPictureButton();
+ this.setNeedsUpdateForDisplayedWidth();
+ this.updateLayoutForDisplayedWidth();
+ },
+
+ handleFullscreenButtonClicked: function(event)
+ {
+ if (this.isFullScreen())
+ this.video.webkitExitFullscreen();
+ else
+ this.video.webkitEnterFullscreen();
+ return true;
+ },
+
+ updateWirelessTargetPickerButton: function() {
+ var wirelessTargetPickerColor;
+ if (this.controls.wirelessTargetPicker.classList.contains('playing'))
+ wirelessTargetPickerColor = "-apple-wireless-playback-target-active";
+ else
+ wirelessTargetPickerColor = "rgba(255,255,255,0.45)";
+ if (window.devicePixelRatio == 2)
+ this.controls.wirelessTargetPicker.style.backgroundImage = "url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15' stroke='" + wirelessTargetPickerColor + "'><defs> <clipPath fill-rule='evenodd' id='cut-hole'><path d='M 0,0.5 L 16,0.5 L 16,15.5 L 0,15.5 z M 0,14.5 L 16,14.5 L 8,5 z'/></clipPath></defs><rect fill='none' clip-path='url(#cut-hole)' x='0.5' y='2' width='15' height='8'/><path stroke='none' fill='" + wirelessTargetPickerColor +"' d='M 3.5,13.25 L 12.5,13.25 L 8,8 z'/></svg>\")";
+ else
+ this.controls.wirelessTargetPicker.style.backgroundImage = "url(\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 15' stroke='" + wirelessTargetPickerColor + "'><defs> <clipPath fill-rule='evenodd' id='cut-hole'><path d='M 0,1 L 16,1 L 16,16 L 0,16 z M 0,15 L 16,15 L 8,5.5 z'/></clipPath></defs><rect fill='none' clip-path='url(#cut-hole)' x='0.5' y='2.5' width='15' height='8'/><path stroke='none' fill='" + wirelessTargetPickerColor +"' d='M 2.75,14 L 13.25,14 L 8,8.75 z'/></svg>\")";
+ },
+
+ handleControlsChange: function()
+ {
+ try {
+ this.updateBase();
+
+ if (this.shouldHaveControls() && !this.hasControls())
+ this.addControls();
+ else if (!this.shouldHaveControls() && this.hasControls())
+ this.removeControls();
+ } catch(e) {
+ if (window.console)
+ console.error(e);
+ }
+ },
+
+ nextRate: function()
+ {
+ return Math.min(this.MaximumSeekRate, Math.abs(this.video.playbackRate * 2));
+ },
+
+ handleSeekBackMouseDown: function(event)
+ {
+ this.actionAfterSeeking = (this.canPlay() ? Controller.PauseAfterSeeking : Controller.PlayAfterSeeking);
+ this.video.play();
+ this.video.playbackRate = this.nextRate() * -1;
+ this.seekInterval = setInterval(this.seekBackFaster.bind(this), this.SeekDelay);
+ },
+
+ seekBackFaster: function()
+ {
+ this.video.playbackRate = this.nextRate() * -1;
+ },
+
+ handleSeekBackMouseUp: function(event)
+ {
+ this.video.playbackRate = this.video.defaultPlaybackRate;
+ if (this.actionAfterSeeking === Controller.PauseAfterSeeking)
+ this.video.pause();
+ else if (this.actionAfterSeeking === Controller.PlayAfterSeeking)
+ this.video.play();
+ if (this.seekInterval)
+ clearInterval(this.seekInterval);
+ },
+
+ handleSeekForwardMouseDown: function(event)
+ {
+ this.actionAfterSeeking = (this.canPlay() ? Controller.PauseAfterSeeking : Controller.PlayAfterSeeking);
+ this.video.play();
+ this.video.playbackRate = this.nextRate();
+ this.seekInterval = setInterval(this.seekForwardFaster.bind(this), this.SeekDelay);
+ },
+
+ seekForwardFaster: function()
+ {
+ this.video.playbackRate = this.nextRate();
+ },
+
+ handleSeekForwardMouseUp: function(event)
+ {
+ this.video.playbackRate = this.video.defaultPlaybackRate;
+ if (this.actionAfterSeeking === Controller.PauseAfterSeeking)
+ this.video.pause();
+ else if (this.actionAfterSeeking === Controller.PlayAfterSeeking)
+ this.video.play();
+ if (this.seekInterval)
+ clearInterval(this.seekInterval);
+ },
+
+ updateDuration: function()
+ {
+ var duration = this.video.duration;
+ this.controls.timeline.min = 0;
+ this.controls.timeline.max = duration;
+
+ this.setIsLive(duration === Number.POSITIVE_INFINITY);
+
+ var timeControls = [this.controls.currentTime, this.controls.remainingTime, this.currentTimeClone, this.remainingTimeClone];
+
+ function removeTimeClass(className) {
+ for (let element of timeControls)
+ element.classList.remove(className);
+ }
+
+ function addTimeClass(className) {
+ for (let element of timeControls)
+ element.classList.add(className);
+ }
+
+ // Reset existing style.
+ removeTimeClass(this.ClassNames.threeDigitTime);
+ removeTimeClass(this.ClassNames.fourDigitTime);
+ removeTimeClass(this.ClassNames.fiveDigitTime);
+ removeTimeClass(this.ClassNames.sixDigitTime);
+
+ if (duration >= 60*60*10)
+ addTimeClass(this.ClassNames.sixDigitTime);
+ else if (duration >= 60*60)
+ addTimeClass(this.ClassNames.fiveDigitTime);
+ else if (duration >= 60*10)
+ addTimeClass(this.ClassNames.fourDigitTime);
+ else
+ addTimeClass(this.ClassNames.threeDigitTime);
+ },
+
+ progressFillStyle: function(context)
+ {
+ var height = this.timelineHeight;
+ var gradient = context.createLinearGradient(0, 0, 0, height);
+ gradient.addColorStop(0, 'rgb(2, 2, 2)');
+ gradient.addColorStop(1, 'rgb(23, 23, 23)');
+ return gradient;
+ },
+
+ updateProgress: function()
+ {
+ this.updateTimelineMetricsIfNeeded();
+ this.drawTimelineBackground();
+ },
+
+ addRoundedRect: function(ctx, x, y, width, height, radius) {
+ ctx.moveTo(x + radius, y);
+ ctx.arcTo(x + width, y, x + width, y + radius, radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
+ ctx.lineTo(x + radius, y + height);
+ ctx.arcTo(x, y + height, x, y + height - radius, radius);
+ ctx.lineTo(x, y + radius);
+ ctx.arcTo(x, y, x + radius, y, radius);
+ },
+
+ drawTimelineBackground: function() {
+ var dpr = window.devicePixelRatio;
+ var width = this.timelineWidth * dpr;
+ var height = this.timelineHeight * dpr;
+
+ if (!width || !height)
+ return;
+
+ var played = this.controls.timeline.value / this.controls.timeline.max;
+ var buffered = 0;
+ for (var i = 0, end = this.video.buffered.length; i < end; ++i)
+ buffered = Math.max(this.video.buffered.end(i), buffered);
+
+ buffered /= this.video.duration;
+
+ var ctx = document.getCSSCanvasContext('2d', this.timelineContextName, width, height);
+
+ width /= dpr;
+ height /= dpr;
+
+ ctx.save();
+ ctx.scale(dpr, dpr);
+ ctx.clearRect(0, 0, width, height);
+
+ var timelineHeight = 3;
+ var trackHeight = 1;
+ var scrubberWidth = 3;
+ var scrubberHeight = 15;
+ var borderSize = 2;
+ var scrubberPosition = Math.max(0, Math.min(width - scrubberWidth, Math.round(width * played)));
+
+ // Draw buffered section.
+ ctx.save();
+ if (this.isAudio())
+ ctx.fillStyle = "rgb(71, 71, 71)";
+ else
+ ctx.fillStyle = "rgb(30, 30, 30)";
+ ctx.fillRect(1, 8, Math.round(width * buffered) - borderSize, trackHeight);
+ ctx.restore();
+
+ // Draw timeline border.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, scrubberPosition, 7, width - scrubberPosition, timelineHeight, timelineHeight / 2.0);
+ this.addRoundedRect(ctx, scrubberPosition + 1, 8, width - scrubberPosition - borderSize , trackHeight, trackHeight / 2.0);
+ ctx.closePath();
+ ctx.clip("evenodd");
+ if (this.isAudio())
+ ctx.fillStyle = "rgb(71, 71, 71)";
+ else
+ ctx.fillStyle = "rgb(30, 30, 30)";
+ ctx.fillRect(0, 0, width, height);
+ ctx.restore();
+
+ // Draw played section.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, 0, 7, width, timelineHeight, timelineHeight / 2.0);
+ ctx.closePath();
+ ctx.clip();
+ if (this.isAudio())
+ ctx.fillStyle = "rgb(116, 116, 116)";
+ else
+ ctx.fillStyle = "rgb(75, 75, 75)";
+ ctx.fillRect(0, 0, width * played, height);
+ ctx.restore();
+
+ // Draw the scrubber.
+ ctx.save();
+ ctx.clearRect(scrubberPosition - 1, 0, scrubberWidth + borderSize, height, 0);
+ ctx.beginPath();
+ this.addRoundedRect(ctx, scrubberPosition, 1, scrubberWidth, scrubberHeight, 1);
+ ctx.closePath();
+ ctx.clip();
+ if (this.isAudio())
+ ctx.fillStyle = "rgb(181, 181, 181)";
+ else
+ ctx.fillStyle = "rgb(140, 140, 140)";
+ ctx.fillRect(0, 0, width, height);
+ ctx.restore();
+
+ ctx.restore();
+ },
+
+ drawVolumeBackground: function() {
+ var dpr = window.devicePixelRatio;
+ var width = this.controls.volume.offsetWidth * dpr;
+ var height = this.controls.volume.offsetHeight * dpr;
+
+ if (!width || !height)
+ return;
+
+ var ctx = document.getCSSCanvasContext('2d', this.volumeContextName, width, height);
+
+ width /= dpr;
+ height /= dpr;
+
+ ctx.save();
+ ctx.scale(dpr, dpr);
+ ctx.clearRect(0, 0, width, height);
+
+ var seekerPosition = this.controls.volume.value;
+ var trackHeight = 1;
+ var timelineHeight = 3;
+ var scrubberRadius = 3.5;
+ var scrubberDiameter = 2 * scrubberRadius;
+ var borderSize = 2;
+
+ var scrubberPosition = Math.round(seekerPosition * (width - scrubberDiameter - borderSize));
+
+
+ // Draw portion of volume under slider thumb.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, 0, 3, scrubberPosition + 2, timelineHeight, timelineHeight / 2.0);
+ ctx.closePath();
+ ctx.clip();
+ ctx.fillStyle = "rgb(75, 75, 75)";
+ ctx.fillRect(0, 0, width, height);
+ ctx.restore();
+
+ // Draw portion of volume above slider thumb.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, scrubberPosition, 3, width - scrubberPosition, timelineHeight, timelineHeight / 2.0);
+ ctx.closePath();
+ ctx.clip();
+ ctx.fillStyle = "rgb(30, 30, 30)";
+ ctx.fillRect(0, 0, width, height);
+ ctx.restore();
+
+ // Clear a hole in the slider for the scrubber.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, scrubberPosition, 0, scrubberDiameter + borderSize, height, (scrubberDiameter + borderSize) / 2.0);
+ ctx.closePath();
+ ctx.clip();
+ ctx.clearRect(0, 0, width, height);
+ ctx.restore();
+
+ // Draw scrubber.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, scrubberPosition + 1, 1, scrubberDiameter, scrubberDiameter, scrubberRadius);
+ ctx.closePath();
+ ctx.clip();
+ if (this.isVolumeSliderActive)
+ ctx.fillStyle = "white";
+ else
+ ctx.fillStyle = "rgb(140, 140, 140)";
+ ctx.fillRect(0, 0, width, height);
+ ctx.restore();
+
+ ctx.restore();
+ },
+
+ formatTimeDescription: function(time)
+ {
+ if (isNaN(time))
+ time = 0;
+ var absTime = Math.abs(time);
+ var intSeconds = Math.floor(absTime % 60).toFixed(0);
+ var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
+ var intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
+
+ var secondString = intSeconds == 1 ? 'Second' : 'Seconds';
+ var minuteString = intMinutes == 1 ? 'Minute' : 'Minutes';
+ var hourString = intHours == 1 ? 'Hour' : 'Hours';
+ if (intHours > 0)
+ return `${intHours} ${this.UIString(hourString)}, ${intMinutes} ${this.UIString(minuteString)}, ${intSeconds} ${this.UIString(secondString)}`;
+ if (intMinutes > 0)
+ return `${intMinutes} ${this.UIString(minuteString)}, ${intSeconds} ${this.UIString(secondString)}`;
+ return `${intSeconds} ${this.UIString(secondString)}`;
+ },
+
+ formatTime: function(time)
+ {
+ if (isNaN(time))
+ time = 0;
+ var absTime = Math.abs(time);
+ var intSeconds = Math.floor(absTime % 60).toFixed(0);
+ var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
+ var intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
+ var sign = time < 0 ? '-' : String();
+
+ if (intHours > 0)
+ return sign + intHours + ':' + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2);
+
+ return sign + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2)
+ },
+
+ updatePlaying: function()
+ {
+ this.setPlaying(!this.canPlay());
+ },
+
+ setPlaying: function(isPlaying)
+ {
+ if (!this.video.controls && !this.isFullScreen())
+ return;
+
+ if (this.isPlaying === isPlaying)
+ return;
+ this.isPlaying = isPlaying;
+
+ if (!isPlaying) {
+ this.controls.panel.classList.add(this.ClassNames.paused);
+ if (this.controls.panelBackground)
+ this.controls.panelBackground.classList.add(this.ClassNames.paused);
+ this.controls.playButton.classList.add(this.ClassNames.paused);
+ this.controls.playButton.setAttribute('aria-label', this.UIString('Play'));
+ this.showControls();
+ } else {
+ this.controls.panel.classList.remove(this.ClassNames.paused);
+ if (this.controls.panelBackground)
+ this.controls.panelBackground.classList.remove(this.ClassNames.paused);
+ this.controls.playButton.classList.remove(this.ClassNames.paused);
+ this.controls.playButton.setAttribute('aria-label', this.UIString('Pause'));
+ this.resetHideControlsTimer();
+ this.canToggleShowControlsButton = true;
+ }
+ },
+
+ updateForShowingControls: function()
+ {
+ this.updateLayoutForDisplayedWidth();
+ this.setNeedsTimelineMetricsUpdate();
+ this.updateTime();
+ this.updateProgress();
+ this.drawVolumeBackground();
+ this.drawTimelineBackground();
+ this.controls.panel.classList.add(this.ClassNames.show);
+ this.controls.panel.classList.remove(this.ClassNames.hidden);
+ if (this.controls.panelBackground) {
+ this.controls.panelBackground.classList.add(this.ClassNames.show);
+ this.controls.panelBackground.classList.remove(this.ClassNames.hidden);
+ }
+ },
+
+ showShowControlsButton: function (shouldShow) {
+ this.showControlsButton.hidden = !shouldShow;
+ if (shouldShow && this.shouldHaveControls())
+ this.showControlsButton.focus();
+ },
+
+ showControls: function(focusControls)
+ {
+ this.updateShouldListenForPlaybackTargetAvailabilityEvent();
+ if (!this.video.controls && !this.isFullScreen())
+ return;
+
+ this.updateForShowingControls();
+ if (this.shouldHaveControls() && !this.controls.panel.parentElement) {
+ this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
+ this.base.appendChild(this.controls.panel);
+ if (focusControls)
+ this.controls.playButton.focus();
+ }
+ this.showShowControlsButton(false);
+ },
+
+ hideControls: function()
+ {
+ if (this.controlsAlwaysVisible())
+ return;
+
+ this.clearHideControlsTimer();
+ this.updateShouldListenForPlaybackTargetAvailabilityEvent();
+ this.controls.panel.classList.remove(this.ClassNames.show);
+ if (this.controls.panelBackground)
+ this.controls.panelBackground.classList.remove(this.ClassNames.show);
+ this.showShowControlsButton(this.isPlayable() && this.isPlaying && this.canToggleShowControlsButton);
+ },
+
+ setNeedsUpdateForDisplayedWidth: function()
+ {
+ this.currentDisplayWidth = 0;
+ },
+
+ scheduleUpdateLayoutForDisplayedWidth: function()
+ {
+ setTimeout(this.updateLayoutForDisplayedWidth.bind(this), 0);
+ },
+
+ isControlVisible: function(control)
+ {
+ if (!control)
+ return false;
+ if (!this.root.contains(control))
+ return false;
+ return !control.classList.contains(this.ClassNames.hidden)
+ },
+
+ updateLayoutForDisplayedWidth: function()
+ {
+ if (!this.controls || !this.controls.panel)
+ return;
+
+ var visibleWidth = this.controls.panel.getBoundingClientRect().width;
+ if (this._pageScaleFactor > 1)
+ visibleWidth *= this._pageScaleFactor;
+
+ if (visibleWidth <= 0 || visibleWidth == this.currentDisplayWidth)
+ return;
+
+ this.currentDisplayWidth = visibleWidth;
+
+ // Filter all the buttons which are not explicitly hidden.
+ var buttons = [this.controls.playButton, this.controls.rewindButton, this.controls.captionButton,
+ this.controls.fullscreenButton, this.controls.pictureInPictureButton,
+ this.controls.wirelessTargetPicker, this.controls.muteBox];
+ var visibleButtons = buttons.filter(this.isControlVisible, this);
+
+ // This tells us how much room we need in order to display every visible button.
+ var visibleButtonWidth = this.ButtonWidth * visibleButtons.length;
+
+ var currentTimeWidth = this.currentTimeClone.getBoundingClientRect().width;
+ var remainingTimeWidth = this.remainingTimeClone.getBoundingClientRect().width;
+
+ // Check if there is enough room for the scrubber.
+ var shouldDropTimeline = (visibleWidth - visibleButtonWidth - currentTimeWidth - remainingTimeWidth) < this.MinimumTimelineWidth;
+ this.controls.timeline.classList.toggle(this.ClassNames.dropped, shouldDropTimeline);
+ this.controls.currentTime.classList.toggle(this.ClassNames.dropped, shouldDropTimeline);
+ this.controls.thumbnailTrack.classList.toggle(this.ClassNames.dropped, shouldDropTimeline);
+ this.controls.remainingTime.classList.toggle(this.ClassNames.dropped, shouldDropTimeline);
+
+ // Then controls in the following order:
+ var removeOrder = [this.controls.wirelessTargetPicker, this.controls.pictureInPictureButton,
+ this.controls.captionButton, this.controls.muteBox, this.controls.rewindButton,
+ this.controls.fullscreenButton];
+ removeOrder.forEach(function(control) {
+ var shouldDropControl = visibleWidth < visibleButtonWidth && this.isControlVisible(control);
+ control.classList.toggle(this.ClassNames.dropped, shouldDropControl);
+ if (shouldDropControl)
+ visibleButtonWidth -= this.ButtonWidth;
+ }, this);
+ },
+
+ controlsAlwaysVisible: function()
+ {
+ if (this.presentationMode() === 'picture-in-picture')
+ return true;
+
+ return this.isAudio() || this.currentPlaybackTargetIsWireless() || this.scrubbing;
+ },
+
+ controlsAreHidden: function()
+ {
+ return !this.controlsAlwaysVisible() && !this.controls.panel.classList.contains(this.ClassNames.show) && !this.controls.panel.parentElement;
+ },
+
+ removeControls: function()
+ {
+ if (this.controls.panel.parentNode)
+ this.controls.panel.parentNode.removeChild(this.controls.panel);
+ this.destroyCaptionMenu();
+ },
+
+ addControls: function()
+ {
+ this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
+ this.base.appendChild(this.controls.panel);
+ this.updateControls();
+ },
+
+ hasControls: function()
+ {
+ return this.controls.panel.parentElement;
+ },
+
+ updateTime: function()
+ {
+ var currentTime = this.video.currentTime;
+ var timeRemaining = currentTime - this.video.duration;
+ this.currentTimeClone.innerText = this.controls.currentTime.innerText = this.formatTime(currentTime);
+ this.controls.currentTime.setAttribute('aria-label', `${this.UIString('Elapsed')} ${this.formatTimeDescription(currentTime)}`);
+ this.controls.timeline.value = this.video.currentTime;
+ this.remainingTimeClone.innerText = this.controls.remainingTime.innerText = this.formatTime(timeRemaining);
+ this.controls.remainingTime.setAttribute('aria-label', `${this.UIString('Remaining')} ${this.formatTimeDescription(timeRemaining)}`);
+
+ // Mark the timeline value in percentage format in accessibility.
+ var timeElapsedPercent = currentTime / this.video.duration;
+ timeElapsedPercent = Math.max(Math.min(1, timeElapsedPercent), 0);
+ this.controls.timeline.setAttribute('aria-valuetext', `${parseInt(timeElapsedPercent * 100)}%`);
+ },
+
+ updateControlsWhileScrubbing: function()
+ {
+ if (!this.scrubbing)
+ return;
+
+ var currentTime = (this.controls.timeline.value / this.controls.timeline.max) * this.video.duration;
+ var timeRemaining = currentTime - this.video.duration;
+ this.currentTimeClone.innerText = this.controls.currentTime.innerText = this.formatTime(currentTime);
+ this.remainingTimeClone.innerText = this.controls.remainingTime.innerText = this.formatTime(timeRemaining);
+ this.drawTimelineBackground();
+ },
+
+ updateReadyState: function()
+ {
+ this.updateStatusDisplay();
+ },
+
+ setStatusHidden: function(hidden)
+ {
+ if (this.statusHidden === hidden)
+ return;
+
+ this.statusHidden = hidden;
+
+ if (hidden) {
+ this.controls.statusDisplay.classList.add(this.ClassNames.hidden);
+ this.controls.currentTime.classList.remove(this.ClassNames.hidden);
+ this.controls.timeline.classList.remove(this.ClassNames.hidden);
+ this.controls.remainingTime.classList.remove(this.ClassNames.hidden);
+ this.setNeedsTimelineMetricsUpdate();
+ this.showControls();
+ } else {
+ this.controls.statusDisplay.classList.remove(this.ClassNames.hidden);
+ this.controls.currentTime.classList.add(this.ClassNames.hidden);
+ this.controls.timeline.classList.add(this.ClassNames.hidden);
+ this.controls.remainingTime.classList.add(this.ClassNames.hidden);
+ this.hideControls();
+ }
+ this.updateWirelessTargetAvailable();
+ },
+
+ trackHasThumbnails: function(track)
+ {
+ return track.kind === 'thumbnails' || (track.kind === 'metadata' && track.label === 'thumbnails');
+ },
+
+ updateThumbnail: function()
+ {
+ for (var i = 0; i < this.video.textTracks.length; ++i) {
+ var track = this.video.textTracks[i];
+ if (this.trackHasThumbnails(track)) {
+ this.controls.thumbnail.classList.remove(this.ClassNames.hidden);
+ return;
+ }
+ }
+
+ this.controls.thumbnail.classList.add(this.ClassNames.hidden);
+ },
+
+ updateCaptionButton: function()
+ {
+ var audioTracks = this.host.sortedTrackListForMenu(this.video.audioTracks);
+ var textTracks = this.host.sortedTrackListForMenu(this.video.textTracks);
+
+ if ((textTracks && textTracks.length) || (audioTracks && audioTracks.length > 1))
+ this.controls.captionButton.classList.remove(this.ClassNames.hidden);
+ else
+ this.controls.captionButton.classList.add(this.ClassNames.hidden);
+ this.setNeedsUpdateForDisplayedWidth();
+ this.updateLayoutForDisplayedWidth();
+ },
+
+ updateCaptionContainer: function()
+ {
+ if (!this.host.textTrackContainer)
+ return;
+
+ var hasClosedCaptions = this.video.webkitHasClosedCaptions;
+ var hasHiddenClass = this.host.textTrackContainer.classList.contains(this.ClassNames.hidden);
+
+ if (hasClosedCaptions && hasHiddenClass)
+ this.host.textTrackContainer.classList.remove(this.ClassNames.hidden);
+ else if (!hasClosedCaptions && !hasHiddenClass)
+ this.host.textTrackContainer.classList.add(this.ClassNames.hidden);
+
+ this.updateBase();
+ this.host.updateTextTrackContainer();
+ },
+
+ buildCaptionMenu: function()
+ {
+ var audioTracks = this.host.sortedTrackListForMenu(this.video.audioTracks);
+ var textTracks = this.host.sortedTrackListForMenu(this.video.textTracks);
+
+ if ((!textTracks || !textTracks.length) && (!audioTracks || !audioTracks.length))
+ return;
+
+ this.captionMenu = document.createElement('div');
+ this.captionMenu.setAttribute('pseudo', '-webkit-media-controls-closed-captions-container');
+ this.captionMenu.setAttribute('id', 'audioAndTextTrackMenu');
+ this.base.appendChild(this.captionMenu);
+ this.captionMenuItems = [];
+
+ var offItem = this.host.captionMenuOffItem;
+ var automaticItem = this.host.captionMenuAutomaticItem;
+ var displayMode = this.host.captionDisplayMode;
+
+ var list = document.createElement('div');
+ this.captionMenu.appendChild(list);
+ list.classList.add(this.ClassNames.list);
+
+ if (audioTracks && audioTracks.length > 1) {
+ var heading = document.createElement('h3');
+ heading.id = 'webkitMediaControlsAudioTrackHeading'; // for AX menu label
+ list.appendChild(heading);
+ heading.innerText = this.UIString('Audio');
+
+ var ul = document.createElement('ul');
+ ul.setAttribute('role', 'menu');
+ ul.setAttribute('aria-labelledby', 'webkitMediaControlsAudioTrackHeading');
+ list.appendChild(ul);
+
+ for (var i = 0; i < audioTracks.length; ++i) {
+ var menuItem = document.createElement('li');
+ menuItem.setAttribute('role', 'menuitemradio');
+ menuItem.setAttribute('tabindex', '-1');
+ this.captionMenuItems.push(menuItem);
+ this.listenFor(menuItem, 'click', this.audioTrackItemSelected);
+ this.listenFor(menuItem, 'keyup', this.handleAudioTrackItemKeyUp);
+ ul.appendChild(menuItem);
+
+ var track = audioTracks[i];
+ menuItem.innerText = this.host.displayNameForTrack(track);
+ menuItem.track = track;
+
+ var itemCheckmark = document.createElement("img");
+ itemCheckmark.classList.add("checkmark-container");
+ menuItem.insertBefore(itemCheckmark, menuItem.firstChild);
+
+ if (track.enabled) {
+ menuItem.classList.add(this.ClassNames.selected);
+ menuItem.setAttribute('tabindex', '0');
+ menuItem.setAttribute('aria-checked', 'true');
+ }
+ }
+ }
+
+ if (textTracks && textTracks.length > 2) {
+ var heading = document.createElement('h3');
+ heading.id = 'webkitMediaControlsClosedCaptionsHeading'; // for AX menu label
+ list.appendChild(heading);
+ heading.innerText = this.UIString('Subtitles');
+
+ var ul = document.createElement('ul');
+ ul.setAttribute('role', 'menu');
+ ul.setAttribute('aria-labelledby', 'webkitMediaControlsClosedCaptionsHeading');
+ list.appendChild(ul);
+
+ for (var i = 0; i < textTracks.length; ++i) {
+ var menuItem = document.createElement('li');
+ menuItem.setAttribute('role', 'menuitemradio');
+ menuItem.setAttribute('tabindex', '-1');
+ this.captionMenuItems.push(menuItem);
+ this.listenFor(menuItem, 'click', this.captionItemSelected);
+ this.listenFor(menuItem, 'keyup', this.handleCaptionItemKeyUp);
+ ul.appendChild(menuItem);
+
+ var track = textTracks[i];
+ menuItem.innerText = this.host.displayNameForTrack(track);
+ menuItem.track = track;
+
+ var itemCheckmark = document.createElement("img");
+ itemCheckmark.classList.add("checkmark-container");
+ menuItem.insertBefore(itemCheckmark, menuItem.firstChild);
+
+ if (track === offItem) {
+ var offMenu = menuItem;
+ continue;
+ }
+
+ if (track === automaticItem) {
+ if (displayMode === 'automatic') {
+ menuItem.classList.add(this.ClassNames.selected);
+ menuItem.setAttribute('tabindex', '0');
+ menuItem.setAttribute('aria-checked', 'true');
+ }
+ continue;
+ }
+
+ if (displayMode != 'automatic' && track.mode === 'showing') {
+ var trackMenuItemSelected = true;
+ menuItem.classList.add(this.ClassNames.selected);
+ menuItem.setAttribute('tabindex', '0');
+ menuItem.setAttribute('aria-checked', 'true');
+ }
+
+ }
+
+ if (offMenu && (displayMode === 'forced-only' || displayMode === 'manual') && !trackMenuItemSelected) {
+ offMenu.classList.add(this.ClassNames.selected);
+ offMenu.setAttribute('tabindex', '0');
+ offMenu.setAttribute('aria-checked', 'true');
+ }
+ }
+
+ // focus first selected menuitem
+ for (var i = 0, c = this.captionMenuItems.length; i < c; i++) {
+ var item = this.captionMenuItems[i];
+ if (item.classList.contains(this.ClassNames.selected)) {
+ item.focus();
+ break;
+ }
+ }
+
+ },
+
+ captionItemSelected: function(event)
+ {
+ this.host.setSelectedTextTrack(event.target.track);
+ this.destroyCaptionMenu();
+ },
+
+ focusSiblingCaptionItem: function(event)
+ {
+ var currentItem = event.target;
+ var pendingItem = false;
+ switch(event.keyCode) {
+ case this.KeyCodes.left:
+ case this.KeyCodes.up:
+ pendingItem = currentItem.previousSibling;
+ break;
+ case this.KeyCodes.right:
+ case this.KeyCodes.down:
+ pendingItem = currentItem.nextSibling;
+ break;
+ }
+ if (pendingItem) {
+ currentItem.setAttribute('tabindex', '-1');
+ pendingItem.setAttribute('tabindex', '0');
+ pendingItem.focus();
+ }
+ },
+
+ handleCaptionItemKeyUp: function(event)
+ {
+ switch (event.keyCode) {
+ case this.KeyCodes.enter:
+ case this.KeyCodes.space:
+ this.captionItemSelected(event);
+ break;
+ case this.KeyCodes.escape:
+ this.destroyCaptionMenu();
+ break;
+ case this.KeyCodes.left:
+ case this.KeyCodes.up:
+ case this.KeyCodes.right:
+ case this.KeyCodes.down:
+ this.focusSiblingCaptionItem(event);
+ break;
+ default:
+ return;
+ }
+ // handled
+ event.stopPropagation();
+ event.preventDefault();
+ },
+
+ audioTrackItemSelected: function(event)
+ {
+ for (var i = 0; i < this.video.audioTracks.length; ++i) {
+ var track = this.video.audioTracks[i];
+ track.enabled = (track == event.target.track);
+ }
+
+ this.destroyCaptionMenu();
+ },
+
+ focusSiblingAudioTrackItem: function(event)
+ {
+ var currentItem = event.target;
+ var pendingItem = false;
+ switch(event.keyCode) {
+ case this.KeyCodes.left:
+ case this.KeyCodes.up:
+ pendingItem = currentItem.previousSibling;
+ break;
+ case this.KeyCodes.right:
+ case this.KeyCodes.down:
+ pendingItem = currentItem.nextSibling;
+ break;
+ }
+ if (pendingItem) {
+ currentItem.setAttribute('tabindex', '-1');
+ pendingItem.setAttribute('tabindex', '0');
+ pendingItem.focus();
+ }
+ },
+
+ handleAudioTrackItemKeyUp: function(event)
+ {
+ switch (event.keyCode) {
+ case this.KeyCodes.enter:
+ case this.KeyCodes.space:
+ this.audioTrackItemSelected(event);
+ break;
+ case this.KeyCodes.escape:
+ this.destroyCaptionMenu();
+ break;
+ case this.KeyCodes.left:
+ case this.KeyCodes.up:
+ case this.KeyCodes.right:
+ case this.KeyCodes.down:
+ this.focusSiblingAudioTrackItem(event);
+ break;
+ default:
+ return;
+ }
+ // handled
+ event.stopPropagation();
+ event.preventDefault();
+ },
+
+ destroyCaptionMenu: function()
+ {
+ if (!this.captionMenu)
+ return;
+
+ this.captionMenuItems.forEach(function(item){
+ this.stopListeningFor(item, 'click', this.captionItemSelected);
+ this.stopListeningFor(item, 'keyup', this.handleCaptionItemKeyUp);
+ }, this);
+
+ // FKA and AX: focus the trigger before destroying the element with focus
+ if (this.controls.captionButton)
+ this.controls.captionButton.focus();
+
+ if (this.captionMenu.parentNode)
+ this.captionMenu.parentNode.removeChild(this.captionMenu);
+ delete this.captionMenu;
+ delete this.captionMenuItems;
+ },
+
+ updateHasAudio: function()
+ {
+ if (this.video.audioTracks.length && !this.currentPlaybackTargetIsWireless())
+ this.controls.muteBox.classList.remove(this.ClassNames.hidden);
+ else
+ this.controls.muteBox.classList.add(this.ClassNames.hidden);
+
+ this.setNeedsUpdateForDisplayedWidth();
+ this.updateLayoutForDisplayedWidth();
+ },
+
+ updateHasVideo: function()
+ {
+ this.controls.panel.classList.toggle(this.ClassNames.noVideo, !this.hasVideo());
+ // The availability of the picture-in-picture button as well as the full-screen
+ // button depends no the value returned by hasVideo(), so make sure we invalidate
+ // the availability of both controls.
+ this.updateFullscreenButtons();
+ },
+
+ updateVolume: function()
+ {
+ if (this.video.muted || !this.video.volume) {
+ this.controls.muteButton.classList.add(this.ClassNames.muted);
+ this.controls.volume.value = 0;
+ } else {
+ this.controls.muteButton.classList.remove(this.ClassNames.muted);
+ this.controls.volume.value = this.video.volume;
+ }
+ this.controls.volume.setAttribute('aria-valuetext', `${parseInt(this.controls.volume.value * 100)}%`);
+ this.drawVolumeBackground();
+ },
+
+ isAudio: function()
+ {
+ return this.video instanceof HTMLAudioElement;
+ },
+
+ clearHideControlsTimer: function()
+ {
+ if (this.hideTimer)
+ clearTimeout(this.hideTimer);
+ this.hideTimer = null;
+ },
+
+ resetHideControlsTimer: function()
+ {
+ if (this.hideTimer) {
+ clearTimeout(this.hideTimer);
+ this.hideTimer = null;
+ }
+
+ if (this.isPlaying)
+ this.hideTimer = setTimeout(this.hideControls.bind(this), this.HideControlsDelay);
+ },
+
+ handlePictureInPictureButtonClicked: function(event) {
+ if (!('webkitSetPresentationMode' in this.video))
+ return;
+
+ if (this.presentationMode() === 'picture-in-picture')
+ this.video.webkitSetPresentationMode('inline');
+ else
+ this.video.webkitSetPresentationMode('picture-in-picture');
+ },
+
+ currentPlaybackTargetIsWireless: function() {
+ if (Controller.gSimulateWirelessPlaybackTarget)
+ return true;
+
+ if (!this.currentTargetIsWireless || this.wirelessPlaybackDisabled)
+ return false;
+
+ return true;
+ },
+
+ updateShouldListenForPlaybackTargetAvailabilityEvent: function() {
+ var shouldListen = true;
+ if (this.video.error)
+ shouldListen = false;
+ if (!this.isAudio() && !this.video.paused && this.controlsAreHidden())
+ shouldListen = false;
+ if (document.hidden)
+ shouldListen = false;
+
+ this.setShouldListenForPlaybackTargetAvailabilityEvent(shouldListen);
+ },
+
+ updateWirelessPlaybackStatus: function() {
+ if (this.currentPlaybackTargetIsWireless()) {
+ var deviceName = "";
+ var deviceType = "";
+ var type = this.host.externalDeviceType;
+ if (type == "airplay") {
+ deviceType = this.UIString('##WIRELESS_PLAYBACK_DEVICE_TYPE##');
+ deviceName = this.UIString('##WIRELESS_PLAYBACK_DEVICE_NAME##', '##DEVICE_NAME##', this.host.externalDeviceDisplayName || "Apple TV");
+ } else if (type == "tvout") {
+ deviceType = this.UIString('##TVOUT_DEVICE_TYPE##');
+ deviceName = this.UIString('##TVOUT_DEVICE_NAME##');
+ }
+
+ this.controls.inlinePlaybackPlaceholderTextTop.innerText = deviceType;
+ this.controls.inlinePlaybackPlaceholderTextBottom.innerText = deviceName;
+ this.controls.inlinePlaybackPlaceholder.setAttribute('aria-label', deviceType + ", " + deviceName);
+ this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.appleTV);
+ this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.hidden);
+ this.controls.wirelessTargetPicker.classList.add(this.ClassNames.playing);
+ if (!this.isFullScreen() && (this.video.offsetWidth <= 250 || this.video.offsetHeight <= 200)) {
+ this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.small);
+ this.controls.inlinePlaybackPlaceholderTextTop.classList.add(this.ClassNames.small);
+ this.controls.inlinePlaybackPlaceholderTextBottom.classList.add(this.ClassNames.small);
+ } else {
+ this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.small);
+ this.controls.inlinePlaybackPlaceholderTextTop.classList.remove(this.ClassNames.small);
+ this.controls.inlinePlaybackPlaceholderTextBottom.classList.remove(this.ClassNames.small);
+ }
+ this.controls.volumeBox.classList.add(this.ClassNames.hidden);
+ this.controls.muteBox.classList.add(this.ClassNames.hidden);
+ this.updateBase();
+ this.showControls();
+ } else {
+ this.controls.inlinePlaybackPlaceholder.classList.add(this.ClassNames.hidden);
+ this.controls.inlinePlaybackPlaceholder.classList.remove(this.ClassNames.appleTV);
+ this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.playing);
+ this.controls.volumeBox.classList.remove(this.ClassNames.hidden);
+ this.controls.muteBox.classList.remove(this.ClassNames.hidden);
+ }
+ this.setNeedsUpdateForDisplayedWidth();
+ this.updateLayoutForDisplayedWidth();
+ this.reconnectControls();
+ this.updateWirelessTargetPickerButton();
+ },
+
+ updateWirelessTargetAvailable: function() {
+ this.currentTargetIsWireless = this.video.webkitCurrentPlaybackTargetIsWireless;
+ this.wirelessPlaybackDisabled = this.video.webkitWirelessVideoPlaybackDisabled;
+
+ var wirelessPlaybackTargetsAvailable = Controller.gSimulateWirelessPlaybackTarget || this.hasWirelessPlaybackTargets;
+ if (this.wirelessPlaybackDisabled)
+ wirelessPlaybackTargetsAvailable = false;
+
+ if (wirelessPlaybackTargetsAvailable && this.isPlayable())
+ this.controls.wirelessTargetPicker.classList.remove(this.ClassNames.hidden);
+ else
+ this.controls.wirelessTargetPicker.classList.add(this.ClassNames.hidden);
+ this.setNeedsUpdateForDisplayedWidth();
+ this.updateLayoutForDisplayedWidth();
+ },
+
+ handleWirelessPickerButtonClicked: function(event)
+ {
+ this.video.webkitShowPlaybackTargetPicker();
+ return true;
+ },
+
+ handleWirelessPlaybackChange: function(event) {
+ this.updateWirelessTargetAvailable();
+ this.updateWirelessPlaybackStatus();
+ this.setNeedsTimelineMetricsUpdate();
+ },
+
+ handleWirelessTargetAvailableChange: function(event) {
+ var wirelessPlaybackTargetsAvailable = event.availability == "available";
+ if (this.hasWirelessPlaybackTargets === wirelessPlaybackTargetsAvailable)
+ return;
+
+ this.hasWirelessPlaybackTargets = wirelessPlaybackTargetsAvailable;
+ this.updateWirelessTargetAvailable();
+ this.setNeedsTimelineMetricsUpdate();
+ },
+
+ setShouldListenForPlaybackTargetAvailabilityEvent: function(shouldListen) {
+ if (!window.WebKitPlaybackTargetAvailabilityEvent || this.isListeningForPlaybackTargetAvailabilityEvent == shouldListen)
+ return;
+
+ if (shouldListen && this.video.error)
+ return;
+
+ this.isListeningForPlaybackTargetAvailabilityEvent = shouldListen;
+ if (shouldListen)
+ this.listenFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange);
+ else
+ this.stopListeningFor(this.video, 'webkitplaybacktargetavailabilitychanged', this.handleWirelessTargetAvailableChange);
+ },
+
+ get scrubbing()
+ {
+ return this._scrubbing;
+ },
+
+ set scrubbing(flag)
+ {
+ if (this._scrubbing == flag)
+ return;
+ this._scrubbing = flag;
+
+ if (this._scrubbing)
+ this.wasPlayingWhenScrubbingStarted = !this.video.paused;
+ else if (this.wasPlayingWhenScrubbingStarted && this.video.paused) {
+ this.video.play();
+ this.resetHideControlsTimer();
+ }
+ },
+
+ get pageScaleFactor()
+ {
+ return this._pageScaleFactor;
+ },
+
+ set pageScaleFactor(newScaleFactor)
+ {
+ if (this._pageScaleFactor === newScaleFactor)
+ return;
+
+ this._pageScaleFactor = newScaleFactor;
+ },
+
+ set usesLTRUserInterfaceLayoutDirection(usesLTRUserInterfaceLayoutDirection)
+ {
+ this.controls.volumeBox.classList.toggle(this.ClassNames.usesLTRUserInterfaceLayoutDirection, usesLTRUserInterfaceLayoutDirection);
+ },
+
+ handleRootResize: function(event)
+ {
+ this.updateLayoutForDisplayedWidth();
+ this.setNeedsTimelineMetricsUpdate();
+ this.updateTimelineMetricsIfNeeded();
+ this.drawTimelineBackground();
+ },
+
+ getCurrentControlsStatus: function ()
+ {
+ var result = {
+ idiom: this.idiom,
+ status: "ok"
+ };
+
+ var elements = [
+ {
+ name: "Show Controls",
+ object: this.showControlsButton,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Status Display",
+ object: this.controls.statusDisplay,
+ styleValues: ["display"],
+ extraProperties: ["textContent"],
+ },
+ {
+ name: "Play Button",
+ object: this.controls.playButton,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Rewind Button",
+ object: this.controls.rewindButton,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Timeline Box",
+ object: this.controls.timelineBox,
+ },
+ {
+ name: "Mute Box",
+ object: this.controls.muteBox,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Fullscreen Button",
+ object: this.controls.fullscreenButton,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "AppleTV Device Picker",
+ object: this.controls.wirelessTargetPicker,
+ styleValues: ["display"],
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Picture-in-picture Button",
+ object: this.controls.pictureInPictureButton,
+ extraProperties: ["parentElement", "hidden"],
+ },
+ {
+ name: "Caption Button",
+ object: this.controls.captionButton,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Timeline",
+ object: this.controls.timeline,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Current Time",
+ object: this.controls.currentTime,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Thumbnail Track",
+ object: this.controls.thumbnailTrack,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Time Remaining",
+ object: this.controls.remainingTime,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Track Menu",
+ object: this.captionMenu,
+ },
+ {
+ name: "Inline playback placeholder",
+ object: this.controls.inlinePlaybackPlaceholder,
+ },
+ {
+ name: "Media Controls Panel",
+ object: this.controls.panel,
+ extraProperties: ["hidden"],
+ },
+ {
+ name: "Control Base Element",
+ object: this.base || null,
+ },
+ ];
+
+ elements.forEach(function (element) {
+ var obj = element.object;
+ delete element.object;
+
+ element.computedStyle = {};
+ if (obj && element.styleValues) {
+ var computedStyle = window.getComputedStyle(obj);
+ element.styleValues.forEach(function (propertyName) {
+ element.computedStyle[propertyName] = computedStyle[propertyName];
+ });
+ delete element.styleValues;
+ }
+
+ element.bounds = obj ? obj.getBoundingClientRect() : null;
+ element.className = obj ? obj.className : null;
+ element.ariaLabel = obj ? obj.getAttribute('aria-label') : null;
+
+ if (element.extraProperties) {
+ element.extraProperties.forEach(function (property) {
+ element[property] = obj ? obj[property] : null;
+ });
+ delete element.extraProperties;
+ }
+
+ element.element = obj;
+ });
+
+ result.elements = elements;
+
+ return JSON.stringify(result);
+ }
+
+};
diff --git a/Source/WebCore/Modules/mediacontrols/mediaControlsBase.css b/Source/WebCore/Modules/mediacontrols/mediaControlsBase.css
new file mode 100644
index 000000000..61cf47e89
--- /dev/null
+++ b/Source/WebCore/Modules/mediacontrols/mediaControlsBase.css
@@ -0,0 +1,758 @@
+/*
+ * 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,
+ * 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;
+ -webkit-align-items: stretch;
+ -webkit-justify-content: flex-end;
+ -webkit-flex-direction: column;
+}
+
+video::-webkit-media-text-track-container,
+audio::-webkit-media-text-track-container {
+ position: relative;
+ -webkit-flex: 1 1 auto;
+}
+
+video::-webkit-media-controls-panel-composited-parent {
+ -webkit-transform: translateZ(0);
+ width: 100%;
+}
+
+video::-webkit-media-controls-panel,
+audio::-webkit-media-controls-panel {
+ box-sizing: border-box;
+ position: relative;
+ bottom: 0;
+ width: 100%;
+ padding-top: 1px;
+ min-height: 25px;
+ height: 25px;
+ line-height: 25px;
+ -webkit-user-select: none;
+ background-color: transparent;
+ background-image: -webkit-linear-gradient(top,
+ rgba(0, 0, 0, .92) 0,
+ rgba(0, 0, 0, .92) 1px,
+ rgba(89, 89, 89, .92) 1px,
+ rgba(89, 89, 89, .92) 2px,
+ rgba(60, 60, 60, .92) 2px,
+ rgba(35, 35, 35, .92) 12px,
+ rgba(30, 30, 30, .92) 12px,
+ rgba(30, 30, 30, .92) 13px,
+ rgba(25, 25, 25, .92) 13px,
+ rgba(17, 17, 17, .92) 100%
+ );
+
+ display: -webkit-flex;
+ -webkit-flex-direction: row;
+ -webkit-align-items: center;
+ -webkit-user-select: none;
+
+ direction: ltr;
+
+ transition: opacity 0.25s linear;
+}
+
+video::-webkit-media-controls-panel {
+ cursor: none;
+ opacity: 0;
+}
+
+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 button,
+audio::-webkit-media-controls-panel button {
+ -webkit-appearance: none;
+ display: block;
+ padding: 0;
+ border: 0;
+ height: 16px;
+ width: 16px;
+ background-color: transparent;
+ color: white;
+ background-origin: content-box;
+ background-repeat: no-repeat;
+ background-position: center;
+ -webkit-filter: drop-shadow(black 0 1px 1px);
+}
+
+video::-webkit-media-controls-panel button:active,
+audio::-webkit-media-controls-panel button:active {
+ -webkit-filter: drop-shadow(white 0 0 10px);
+}
+
+video::-webkit-media-controls-rewind-button,
+audio::-webkit-media-controls-rewind-button {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 17"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.44444" stop-color="rgb(216, 216, 216)"/><stop offset="0.44444" stop-color="rgb(208, 208, 208)"/><stop offset="0.55555" stop-color="rgb(208, 208, 208)"/><stop offset="0.55555" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="m 7.9131,2 0,-1.548 -2.586,2.155 0,-2.171 -2.582,2.208 2.582,2.175 0,-2.139 2.586,2.155 0,-1.276 c 3.138,0.129 5.491,2.681 5.543,5.838 l -1.031,0 0.016,0.215 1.015,0 c -0.06,3.19 -2.629,5.765 -5.819,5.833 l 0,-1.018 -0.214,0 0,1.021 c -3.21,-0.047 -5.801,-2.631 -5.862,-5.836 l 1.045,0 -0.016,-0.215 -1.045,0 c -0.052,-0.288 -0.318,-0.654 -0.766,-0.654 -0.538,0 -0.755,0.484 -0.755,0.75 0,4.146 3.331,7.506 7.476,7.506 4.146,0 7.506,-3.36 7.506,-7.506 0,-4.059 -3.066,-7.357 -7.093,-7.493" fill="url(#gradient)"/><path d="m 5.1729,11.0518 c -0.38,0 -0.668,-0.129 -0.945,-0.366 -0.083,-0.071 -0.186,-0.134 -0.338,-0.134 -0.277,0 -0.511,0.238 -0.511,0.521 0,0.154 0.083,0.301 0.179,0.383 0.394,0.346 0.911,0.563 1.601,0.563 1.077,0 1.739,-0.681 1.739,-1.608 l 0,-0.013 c 0,-0.911 -0.641,-1.265 -1.296,-1.376 l 0.945,-0.919 c 0.193,-0.19 0.317,-0.337 0.317,-0.604 0,-0.294 -0.228,-0.477 -0.538,-0.477 l -2.354,0 c -0.248,0 -0.455,0.21 -0.455,0.464 0,0.253 0.207,0.463 0.455,0.463 l 1.485,0 -0.939,0.961 c -0.166,0.169 -0.228,0.295 -0.228,0.444 0,0.25 0.207,0.463 0.455,0.463 l 0.166,0 c 0.594,0 0.945,0.222 0.945,0.624 l 0,0.012 c 0,0.367 -0.282,0.599 -0.683,0.599" fill="url(#gradient)"/><path d="m 10.354,9.5342 c 0,0.876 -0.379,1.525 -0.979,1.525 -0.599,0 -0.992,-0.655 -0.992,-1.539 l 0,-0.012 c 0,-0.884 0.388,-1.527 0.979,-1.527 0.592,0 0.992,0.663 0.992,1.539 l 0,0.014 z m -0.979,-2.512 c -1.197,0 -2.008,1.097 -2.008,2.498 l 0,0.014 c 0,1.401 0.792,2.484 1.995,2.484 1.205,0 2.01,-1.097 2.01,-2.498 l 0,-0.012 c 0,-1.402 -0.805,-2.486 -1.997,-2.486" fill="url(#gradient)"/></svg>');
+ width: 16px;
+ height: 18px;
+ margin-bottom: 1px;
+ margin-left: 6px;
+ margin-right: 4px;
+}
+
+video::-webkit-media-controls-play-button,
+audio::-webkit-media-controls-play-button {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 0,0 v 16 h 6 v -16 h -6 z" fill="url(#gradient)"/><path d="M 9,0 v 16 h 6 v -16 h -6 z" fill="url(#gradient)"/></svg>');
+ margin-left: 6px;
+ margin-right: 1px;
+}
+
+video::-webkit-media-controls-play-button.paused,
+audio::-webkit-media-controls-play-button.paused {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 0,0 15,7 0,15 z" fill="url(#gradient)"/></svg>');
+}
+
+video::-webkit-media-controls-panel .mute-box,
+audio::-webkit-media-controls-panel .mute-box {
+ width: 22px;
+ height: 22px;
+ margin-right: 2px;
+
+ 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 {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="m 0,9 0,-4 3,0 3,-3 0,10 -3,-3 -3,0 z" style="fill:url(#gradient) "/><path d="m 10.449,1.087 c 1.963,1.055 3.322,3.291 3.322,5.881 0,2.642 -1.402,4.913 -3.424,5.945" style="fill:none;stroke:url(#gradient);stroke-width:1.25;stroke-linecap:round;"/><path d="m 9.13,3.134 c 1.289,0.681 2.181,2.142 2.181,3.835 0,1.743001 -0.939,3.24 -2.285,3.897" style="fill:none;stroke:url(#gradient);stroke-width:1.25;stroke-linecap:round;"/><path d="M 7.794,5.175 C 8.403001,5.491 8.827001,6.167 8.827001,6.971 8.827001,7.818 8.356,8.537001 7.688,8.826" style="fill:none;stroke:url(#gradient);stroke-width:1.25;stroke-linecap:round;"/></svg>');
+ width: 14px;
+}
+
+video::-webkit-media-controls-panel .volume-box,
+audio::-webkit-media-controls-panel .volume-box {
+ position: absolute;
+ box-sizing: border-box;
+ height: 22px;
+ bottom: 0;
+ left: 0;
+
+ -webkit-transform: rotate(-90deg);
+ -webkit-transform-origin: 11px 11px;
+
+ background-color: transparent;
+ background-image: -webkit-linear-gradient(
+ left,
+ rgba(17, 17, 17, 0.92),
+ rgba(42, 42, 42, 0.92)
+ );
+ border: 1px solid rgba(0, 0, 0, 0.95);
+ border-radius: 12px;
+
+ display: -webkit-flex;
+ -webkit-flex-direction: row;
+ -webkit-align-items: center;
+ -webkit-justify-content: flex-end;
+
+ opacity: 0;
+ /* make zero width (rather than display:none) for AX and FKA */
+ width: 0; /* will become 114px when shown */
+
+}
+
+/* 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 */
+ width: 114px;
+}
+
+audio::-webkit-media-controls-volume-slider,
+video::-webkit-media-controls-volume-slider {
+ -webkit-appearance: none !important;
+ box-sizing: border-box !important;
+ height: 10px !important;
+ width: 80px !important;
+ padding: 0 !important;
+ margin-right: 6px !important;
+
+ border-radius: 5px !important;
+ background-color: transparent !important;
+ background-image: -webkit-linear-gradient(
+ top,
+ rgba(15, 15, 15, .85) 0,
+ rgba(23, 23, 23, .85) 50%,
+ rgba(15, 15, 15, .85) 100%
+ ) !important;
+ border: 1px solid rgba(0, 0, 0, 0.875) !important;
+}
+
+video::-webkit-media-controls-volume-slider::-webkit-slider-thumb,
+audio::-webkit-media-controls-volume-slider::-webkit-slider-thumb {
+ -webkit-appearance: none !important;
+ width: 8px !important;
+ height: 8px !important;
+ border-radius: 4px !important;
+ background-color: transparent !important;
+
+ /* rotateZ() forces the layer into compositing mode.
+ Slider thumbs are small, so forcing a compositing layer is inexpensive,
+ and it keeps the slider from having to repaint while sliding. */
+ -webkit-transform: rotateZ(0) !important;
+ background-image: -webkit-linear-gradient(
+ left,
+ rgba(99, 99, 99, 1),
+ rgba(144, 144, 144, 1)
+ ) !important;
+ box-shadow: inset -1px 0 0 rgba(255, 255, 255, .5), 0 1px rgba(255, 255, 255, .14) !important;
+}
+video::-webkit-media-controls-volume-slider::-webkit-slider-thumb::-webkit-slider-thumb:active,
+video::-webkit-media-controls-volume-slider::-webkit-slider-thumb:active::-webkit-slider-thumb,
+audio::-webkit-media-controls-volume-slider::-webkit-slider-thumb::-webkit-slider-thumb:active,
+audio::-webkit-media-controls-volume-slider::-webkit-slider-thumb:active::-webkit-slider-thumb {
+ background-image: -webkit-linear-gradient(
+ right top,
+ rgba(160, 160, 160, 1),
+ rgba(221, 221, 221, 1)
+ ) !important;
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="m 0,9 0,-4 3,0 3,-3 0,10 -3,-3 -3,0 z" fill="url(#gradient)"/></svg>');
+}
+
+video::-webkit-media-controls-toggle-closed-captions-button,
+audio::-webkit-media-controls-toggle-closed-captions-button {
+ width: 16px;
+ height: 16px;
+ margin: 0 7px;
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 102 105"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.46875" stop-color="rgb(216, 216, 216)"/><stop offset="0.46875" stop-color="rgb(208, 208, 208)"/><stop offset="0.53125" stop-color="rgb(208, 208, 208)"/><stop offset="0.53125" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M98.766,43.224c0-23.163-21.775-41.94-48.637-41.94c-26.859,0-48.635,18.777-48.635,41.94c0,18.266,13.546,33.796,32.444,39.549c1.131,8.356,26.037,24.255,22.864,19.921c-4.462-6.096-5.159-13.183-5.07-17.566C77.85,84.397,98.766,65.923,98.766,43.224z" fill="url(#gradient)"/></svg>');
+ 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.85);
+ border: 3px solid rgba(128, 128, 128, 0.75);
+ border-radius: 10px;
+ 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;
+ font-family: "Helvetica Bold", Helvetica, sans-serif;
+ font-size: 10pt;
+ -webkit-user-select: none;
+}
+
+video::-webkit-media-controls-closed-captions-container h3,
+audio::-webkit-media-controls-closed-captions-container h3 {
+ margin: 0;
+ color: rgb(117, 117, 117);
+ text-shadow: 0 1px 0 black;
+ -webkit-margin-start: 23px;
+ padding-top: 4px;
+ font-weight: bold;
+ font-size: 10pt;
+}
+
+video::-webkit-media-controls-closed-captions-container ul,
+audio::-webkit-media-controls-closed-captions-container ul {
+ list-style-type: none;
+ margin: 0 0 4px 0;
+ padding: 0;
+ font-weight: bold;
+}
+
+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: 37px;
+ padding-right: 35px;
+ 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-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, 0.3)), color-stop(1, rgba(255, 255, 255, 0.2)));
+}
+
+video::-webkit-media-controls-closed-captions-container li:hover,
+audio::-webkit-media-controls-closed-captions-container li:hover {
+ background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgb(79, 112, 246)), color-stop(1, rgb(26, 68, 243)));
+ border-top: 1px solid rgb(70, 103, 234);
+ border-bottom: 1px solid rgb(3, 54, 229);
+}
+
+video::-webkit-media-controls-closed-captions-container li.selected::before,
+audio::-webkit-media-controls-closed-captions-container li.selected::before {
+ display: block;
+ content: "";
+ position: absolute;
+ top: 0.25em;
+ width: 1.1em;
+ height: 1.1em;
+ -webkit-margin-start: -20px;
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><polygon fill="rgb(163, 163, 163)" points="252.301,4.477 126.667,194.104 43.358,108.3 6.868,161.408 132.515,290.814 297.732,49.926"/></svg>');
+ background-repeat: no-repeat;
+}
+
+video::-webkit-media-controls-closed-captions-container li.selected:hover::before,
+audio::-webkit-media-controls-closed-captions-container li.selected:hover::before {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><polygon fill="white" points="252.301,4.477 126.667,194.104 43.358,108.3 6.868,161.408 132.515,290.814 297.732,49.926"/></svg>');
+}
+
+video::-webkit-media-controls-fullscreen-button,
+audio::-webkit-media-controls-fullscreen-button {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" transform="rotate(90,0,0)"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 14,1 m 0,6 -2,-2 -2,2 c 0,0 -1,1 -2,0 -1,-1 0,-2 0,-2 l 2,-2 -2,-2 6,0 z" style="fill:url(#gradient) "/><path d="M 1,14 m 0,-6 2,2 2,-2 c 0,0 1,-1 2,0 1,1 0,2 0,2 l -2,2 2,2 -6,0 z" style="fill:url(#gradient) "/></svg>');
+ margin: 0 7px;
+}
+video::-webkit-media-controls-fullscreen-button.exit,
+audio::-webkit-media-controls-fullscreen-button.exit {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" transform="rotate(90,0,0)"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 7,8 m 0,6 -2,-2 -2,2 c 0,0 -1,1 -2,0 -1,-1 0,-2 0,-2 l 2,-2 -2,-2 6,0 z" style="fill:url(#gradient) "/><path d="M 8,7 m 0,-6 2,2 2,-2 c 0,0 1,-1 2,0 1,1 0,2 0,2 l -2,2 2,2 -6,0 z" style="fill:url(#gradient) "/></svg>');
+}
+video::-webkit-media-controls-status-display,
+audio::-webkit-media-controls-status-display {
+ cursor: default;
+ font: -webkit-small-control;
+ font-size: 9px;
+ overflow: hidden;
+ color: white;
+ text-shadow: black 0px 1px 1px;
+
+ 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-timeline,
+audio::-webkit-media-controls-timeline {
+ -webkit-appearance: none !important;
+ -webkit-flex: 1 1 0 !important;
+ height: 9px !important;
+ margin: 0 !important;
+
+ border-radius: 4.5px !important;
+ background-color: rgb(74, 74, 74) !important;
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, .68), 0 1px rgba(255, 255, 255, .08) !important;
+}
+video::-webkit-media-controls-timeline::-webkit-slider-thumb,
+audio::-webkit-media-controls-timeline::-webkit-slider-thumb {
+ -webkit-appearance: none !important;
+ width:6px !important;
+ height: 6px !important;
+ background-color: white !important;
+
+ /* rotateZ() forces the layer into compositing mode.
+ Slider thumbs are small, so forcing a compositing layer is inexpensive,
+ and it keeps the slider from having to repaint while sliding. */
+ -webkit-transform: translateY(1px) rotateZ(-45deg) !important;
+
+ background-image: -webkit-gradient(
+ linear,
+ left bottom,
+ right top,
+ color-stop(0, rgba(99, 99, 99, 1)),
+ color-stop(1, rgba(144, 144, 144, 1))
+ ) !important;
+}
+video::-webkit-media-controls-timeline::-webkit-slider-thumb:active,
+video::-webkit-media-controls-timeline:active::-webkit-slider-thumb,
+audio::-webkit-media-controls-timeline::-webkit-slider-thumb:active,
+audio::-webkit-media-controls-timeline:active::-webkit-slider-thumb,
+ {
+ background-image: -webkit-gradient(
+ linear,
+ left bottom,
+ right top,
+ color-stop(0, rgba(160, 160, 160, 1)),
+ color-stop(1, rgba(221, 221, 221, 1))
+ ) !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;
+ -webkit-justify-content: center;
+ -webkit-align-items: center;
+ cursor: default;
+ font: -webkit-small-control;
+ font-size: 9px;
+ overflow-y: hidden;
+ overflow-x: hidden;
+ width: 45px;
+ min-width: 45px;
+ color: white;
+ text-shadow: black 0px 1px 1px;
+ letter-spacing: normal;
+ word-spacing: normal;
+ line-height: normal;
+ text-transform: none;
+ text-indent: 0px;
+ text-decoration: none;
+}
+
+video::-webkit-media-controls-timeline-container .hour-long-time,
+audio::-webkit-media-controls-timeline-container .hour-long-time {
+ min-width: 67px;
+}
+
+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;
+ 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;
+ height: 9px;
+ 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;
+}
+
+/* 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;
+
+ width: 440px !important;
+ height: 60px !important;
+ margin: 0 auto 50px auto !important;
+ padding-top: 10px !important;
+
+ background: -webkit-linear-gradient(top,
+ rgba(45, 45, 45, .97) 0,
+ rgba(30, 30, 30, .97) 19px,
+ rgba(25, 25, 25, .97) 19px,
+ rgba(25, 25, 25, .97) 20px,
+ rgba(19, 19, 19, .97) 20px,
+ rgba(12, 12, 12, .97) 100%
+ ) !important;
+
+ box-shadow:
+ inset 0 -1px 1px rgba(0, 0, 0, 0.5),
+ inset 0 1px 0 0px rgba(255, 255, 255, 0.15),
+ inset 0 -1px 0 0px rgba(202, 202, 202, 0.09),
+ 0 0 0 1px rgba(0, 0, 0, 0.5);
+ border-radius: 8px !important;
+
+ transition: opacity 0.3s linear !important;
+}
+
+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;
+ opacity: 1;
+ left: 11px;
+ top: 13px;
+ width: 90px;
+ height: 14px;
+ display: -webkit-flex;
+ -webkit-flex-direction: row;
+ -webkit-align-items: center;
+ background-image: none;
+ border: none;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-volume-slider {
+ height: 6px !important;
+ border-radius: 3px !important;
+ background-image: -webkit-linear-gradient(top,
+ rgba(16, 16, 16, .300) 0,
+ rgba(9, 9, 9, .629) 100%
+ ) !important;
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, .68), 0 1px rgba(255, 255, 255, .08) !important;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-volume-slider::-webkit-slider-thumb {
+ width: 10px !important;
+ height: 10px !important;
+ border-radius: 5px !important;
+
+ /* rotateZ() forces the layer into compositing mode.
+ Slider thumbs are small, so forcing a compositing layer is inexpensive,
+ and it keeps the slider from having to repaint while sliding. */
+ -webkit-transform: rotateZ(270deg) !important;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-play-button {
+ position: absolute;
+
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 22"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.478" stop-color="rgb(216, 216, 216)"/><stop offset="0.478" stop-color="rgb(208, 208, 208)"/><stop offset="0.522" stop-color="rgb(208, 208, 208)"/><stop offset="0.522" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 0,0 0,22 8,22 8,0 z" fill="url(#gradient)"/><path d="M 13,0 13,22 21,22 21,0 z" fill="url(#gradient)"/></svg>');
+
+ width: 22px;
+ height: 23px;
+ left: 209px;
+ top: 9px;
+ margin: 0;
+ padding: 0;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-play-button.paused {
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 22"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.478" stop-color="rgb(216, 216, 216)"/><stop offset="0.478" stop-color="rgb(208, 208, 208)"/><stop offset="0.522" stop-color="rgb(208, 208, 208)"/><stop offset="0.522" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 0,0 21,11 0,22 z" fill="url(#gradient)"/></svg>');
+}
+
+video:-webkit-full-screen::-webkit-media-controls-rewind-button {
+ position: absolute;
+ left: 162px;
+ top: 13px;
+ width: 18px;
+ height: 18px;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-seek-back-button {
+ position: absolute;
+
+
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 15"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 22,0 11,7 22,15 z" fill="url(#gradient)"/><path d="M 11,0 0,7 11,15 z" fill="url(#gradient)"/></svg>');
+
+ width: 23px;
+ height: 16px;
+ left: 156px;
+ top: 13px;
+}
+
+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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 15"><linearGradient id="gradient" x2="0" y2="100%" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(216, 216, 216)"/><stop offset="0.4375" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(208, 208, 208)"/><stop offset="0.5" stop-color="rgb(200, 200, 200)"/><stop offset="1" stop-color="rgb(208, 208, 208)"/></linearGradient><path d="M 0,0 11,7 0,15 z" fill="url(#gradient)"/><path d="M 11,0 22,7 11,15 z" fill="url(#gradient)"/></svg>');
+
+ width: 23px;
+ height: 16px;
+ left: 256px;
+ top: 13px;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-timeline-container {
+ height: auto;
+ width: 420px;
+ position: absolute;
+ bottom: 9px;
+ left: 8px;
+ right: 8px;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-status-display {
+ width: 420px;
+ position: absolute;
+ bottom: 7px;
+ left: 8px;
+ right: 8px;
+}
+
+video:-webkit-full-screen::-webkit-media-controls-closed-captions-container {
+ bottom: 114px;
+ 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;
+
+ 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;
+}
diff --git a/Source/WebCore/Modules/mediacontrols/mediaControlsBase.js b/Source/WebCore/Modules/mediacontrols/mediaControlsBase.js
new file mode 100644
index 000000000..f78696d64
--- /dev/null
+++ b/Source/WebCore/Modules/mediacontrols/mediaControlsBase.js
@@ -0,0 +1,1336 @@
+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.hasVisualMedia = false;
+
+ this.addVideoListeners();
+ this.createBase();
+ this.createControls();
+ this.updateBase();
+ this.updateControls();
+ this.updateDuration();
+ this.updateProgress();
+ this.updateTime();
+ this.updateReadyState();
+ this.updatePlaying();
+ this.updateThumbnail();
+ this.updateCaptionButton();
+ this.updateCaptionContainer();
+ this.updateFullscreenButton();
+ this.updateVolume();
+ this.updateHasAudio();
+ this.updateHasVideo();
+};
+
+/* Enums */
+Controller.InlineControls = 0;
+Controller.FullScreenControls = 1;
+
+Controller.PlayAfterSeeking = 0;
+Controller.PauseAfterSeeking = 1;
+
+/* Globals */
+Controller.gLastTimelineId = 0;
+
+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',
+ },
+ HideControlsDelay: 4 * 1000,
+ RewindAmount: 30,
+ MaximumSeekRate: 8,
+ SeekDelay: 1500,
+ ClassNames: {
+ active: 'active',
+ exit: 'exit',
+ failed: 'failed',
+ hidden: 'hidden',
+ hiding: 'hiding',
+ hourLongTime: 'hour-long-time',
+ list: 'list',
+ muteBox: 'mute-box',
+ muted: 'muted',
+ paused: 'paused',
+ playing: 'playing',
+ selected: 'selected',
+ show: 'show',
+ thumbnail: 'thumbnail',
+ thumbnailImage: 'thumbnail-image',
+ thumbnailTrack: 'thumbnail-track',
+ volumeBox: 'volume-box',
+ noVideo: 'no-video',
+ down: 'down',
+ out: 'out',
+ },
+ KeyCodes: {
+ enter: 13,
+ escape: 27,
+ space: 32,
+ pageUp: 33,
+ pageDown: 34,
+ end: 35,
+ home: 36,
+ left: 37,
+ up: 38,
+ right: 39,
+ down: 40
+ },
+
+ extend: function(child)
+ {
+ for (var property in this) {
+ if (!child.hasOwnProperty(property))
+ child[property] = this[property];
+ }
+ },
+
+ 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.updateHasAudio);
+ this.listenFor(this.video.audioTracks, 'addtrack', this.updateHasAudio);
+ this.listenFor(this.video.audioTracks, 'removetrack', this.updateHasAudio);
+
+ /* 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'] });
+ },
+
+ 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.updateHasAudio);
+ this.stopListeningFor(this.video.audioTracks, 'addtrack', this.updateHasAudio);
+ this.stopListeningFor(this.video.audioTracks, 'removetrack', this.updateHasAudio);
+
+ /* 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);
+ },
+
+ 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(base, 'mouseout', this.handleWrapperMouseOut);
+ if (this.host.textTrackContainer)
+ base.appendChild(this.host.textTrackContainer);
+ },
+
+ shouldHaveAnyUI: function()
+ {
+ return this.shouldHaveControls() || (this.video.textTracks && this.video.textTracks.length);
+ },
+
+ shouldHaveControls: function()
+ {
+ return this.video.controls || this.isFullScreen();
+ },
+
+ setNeedsTimelineMetricsUpdate: function()
+ {
+ this.timelineMetricsNeedsUpdate = true;
+ },
+
+ updateTimelineMetricsIfNeeded: function()
+ {
+ if (this.timelineMetricsNeedsUpdate) {
+ 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 panelCompositedParent = this.controls.panelCompositedParent = document.createElement('div');
+ panelCompositedParent.setAttribute('pseudo', '-webkit-media-controls-panel-composited-parent');
+
+ 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);
+
+ 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');
+ this.timelineID = ++Controller.gLastTimelineId;
+ timeline.setAttribute('pseudo', '-webkit-media-controls-timeline');
+ timeline.setAttribute('aria-label', this.UIString('Duration'));
+ timeline.style.backgroundImage = '-webkit-canvas(timeline-' + this.timelineID + ')';
+ timeline.type = 'range';
+ timeline.value = 0;
+ this.listenFor(timeline, 'input', 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);
+ timeline.step = .01;
+
+ 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);
+
+ var muteButton = this.controls.muteButton = document.createElement('button');
+ muteButton.setAttribute('pseudo', '-webkit-media-controls-mute-button');
+ muteButton.setAttribute('aria-label', this.UIString('Mute'));
+ 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 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 = .01;
+ this.listenFor(volume, 'input', this.handleVolumeSliderInput);
+
+ 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', 'audioTrackMenu');
+ 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);
+ },
+
+ setControlsType: function(type)
+ {
+ if (type === this.controlsType)
+ return;
+ this.controlsType = type;
+
+ this.reconnectControls();
+ },
+
+ 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.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()
+ {
+ if (!this.isLive)
+ this.controls.panel.appendChild(this.controls.rewindButton);
+ this.controls.panel.appendChild(this.controls.playButton);
+ 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.volume);
+ this.controls.muteBox.appendChild(this.controls.muteButton);
+ this.controls.panel.appendChild(this.controls.captionButton);
+ if (!this.isAudio())
+ 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.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.captionButton);
+ if (!this.isAudio())
+ 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.setNeedsTimelineMetricsUpdate();
+ },
+
+ updateStatusDisplay: function(event)
+ {
+ 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.video.networkState === HTMLMediaElement.NETWORK_LOADING)
+ this.controls.statusDisplay.innerText = this.UIString('Loading');
+ else
+ this.controls.statusDisplay.innerText = '';
+
+ this.setStatusHidden(!this.isLive && this.video.readyState > HTMLMediaElement.HAVE_NOTHING && !this.video.error);
+ },
+
+ 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.hasVisualMedia = this.video.videoTracks && this.video.videoTracks.length > 0;
+ this.updateReadyState();
+ this.updateDuration();
+ this.updateCaptionButton();
+ this.updateCaptionContainer();
+ this.updateFullscreenButton();
+ this.updateProgress();
+ },
+
+ handleTimeUpdate: function(event)
+ {
+ if (!this.scrubbing)
+ this.updateTime();
+ },
+
+ handleDurationChange: function(event)
+ {
+ this.updateDuration();
+ this.updateTime(true);
+ this.updateProgress(true);
+ },
+
+ 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();
+ },
+
+ isFullScreen: function()
+ {
+ return this.video.webkitDisplayingFullscreen;
+ },
+
+ handleFullscreenChange: function(event)
+ {
+ this.updateBase();
+ this.updateControls();
+
+ 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();
+ }
+ },
+
+ handleWrapperMouseMove: function(event)
+ {
+ 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.panel)
+ 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) > 0)
+ this.controls.panel.classList.remove(this.ClassNames.hidden);
+ else
+ this.controls.panel.classList.add(this.ClassNames.hidden);
+ },
+
+ handlePanelClick: function(event)
+ {
+ // Prevent clicks in the panel from playing or pausing the video in a MediaDocument.
+ event.preventDefault();
+ },
+
+ handleRewindButtonClicked: function(event)
+ {
+ var newTime = Math.max(
+ this.video.currentTime - this.RewindAmount,
+ this.video.seekable.start(0));
+ this.video.currentTime = newTime;
+ return true;
+ },
+
+ canPlay: function()
+ {
+ return this.video.paused || this.video.ended || this.video.readyState < HTMLMediaElement.HAVE_METADATA;
+ },
+
+ handlePlayButtonClicked: function(event)
+ {
+ if (this.canPlay())
+ this.video.play();
+ else
+ this.video.pause();
+ return true;
+ },
+
+ handleTimelineChange: function(event)
+ {
+ this.video.fastSeek(this.controls.timeline.value);
+ },
+
+ handleTimelineDown: function(event)
+ {
+ this.controls.thumbnail.classList.add(this.ClassNames.show);
+ },
+
+ handleTimelineUp: function(event)
+ {
+ this.controls.thumbnail.classList.remove(this.ClassNames.show);
+ },
+
+ handleTimelineMouseOver: function(event)
+ {
+ this.controls.thumbnail.classList.add(this.ClassNames.show);
+ },
+
+ handleTimelineMouseOut: function(event)
+ {
+ this.controls.thumbnail.classList.remove(this.ClassNames.show);
+ },
+
+ handleTimelineMouseMove: function(event)
+ {
+ if (this.controls.thumbnail.classList.contains(this.ClassNames.hidden))
+ return;
+
+ this.updateTimelineMetricsIfNeeded();
+ this.controls.thumbnail.classList.add(this.ClassNames.show);
+ var localPoint = webkitConvertPointFromPageToNode(this.controls.timeline, new WebKitPoint(event.clientX, event.clientY));
+ var percent = (localPoint.x - this.timelineLeft) / this.timelineWidth;
+ percent = Math.max(Math.min(1, percent), 0);
+ this.controls.thumbnail.style.left = percent * 100 + '%';
+
+ var thumbnailTime = percent * this.video.duration;
+ for (var i = 0; i < this.video.textTracks.length; ++i) {
+ var track = this.video.textTracks[i];
+ if (!this.trackHasThumbnails(track))
+ continue;
+
+ if (!track.cues)
+ continue;
+
+ for (var j = 0; j < track.cues.length; ++j) {
+ var cue = track.cues[j];
+ if (thumbnailTime >= cue.startTime && thumbnailTime < cue.endTime) {
+ this.controls.thumbnailImage.src = cue.text;
+ return;
+ }
+ }
+ }
+ },
+
+ handleTimelineMouseDown: function(event)
+ {
+ this.scrubbing = true;
+ },
+
+ handleTimelineMouseUp: function(event)
+ {
+ this.scrubbing = false;
+
+ // Do a precise seek when we lift the mouse:
+ this.video.currentTime = this.controls.timeline.value;
+ },
+
+ handleMuteButtonClicked: function(event)
+ {
+ this.video.muted = !this.video.muted;
+ if (this.video.muted)
+ this.controls.muteButton.setAttribute('aria-label', this.UIString('Unmute'));
+ return true;
+ },
+
+ handleMinButtonClicked: function(event)
+ {
+ if (this.video.muted) {
+ this.video.muted = false;
+ this.controls.muteButton.setAttribute('aria-label', this.UIString('Mute'));
+ }
+ this.video.volume = 0;
+ return true;
+ },
+
+ handleMaxButtonClicked: function(event)
+ {
+ if (this.video.muted) {
+ this.video.muted = false;
+ this.controls.muteButton.setAttribute('aria-label', this.UIString('Mute'));
+ }
+ this.video.volume = 1;
+ },
+
+ handleVolumeSliderInput: function(event)
+ {
+ if (this.video.muted) {
+ this.video.muted = false;
+ this.controls.muteButton.setAttribute('aria-label', this.UIString('Mute'));
+ }
+ this.video.volume = this.controls.volume.value;
+ },
+
+ handleCaptionButtonClicked: function(event)
+ {
+ if (this.captionMenu)
+ this.destroyCaptionMenu();
+ else
+ this.buildCaptionMenu();
+ return true;
+ },
+
+ updateFullscreenButton: function()
+ {
+ this.controls.fullscreenButton.classList.toggle(this.ClassNames.hidden, (!this.video.webkitSupportsFullscreen || !this.hasVisualMedia));
+ },
+
+ handleFullscreenButtonClicked: function(event)
+ {
+ if (this.isFullScreen())
+ this.video.webkitExitFullscreen();
+ else
+ this.video.webkitEnterFullscreen();
+ return true;
+ },
+
+ handleControlsChange: function()
+ {
+ try {
+ this.updateBase();
+
+ if (this.shouldHaveControls())
+ this.addControls();
+ else
+ this.removeControls();
+ } catch(e) {
+ if (window.console)
+ console.error(e);
+ }
+ },
+
+ nextRate: function()
+ {
+ return Math.min(this.MaximumSeekRate, Math.abs(this.video.playbackRate * 2));
+ },
+
+ handleSeekBackMouseDown: function(event)
+ {
+ this.actionAfterSeeking = (this.canPlay() ? Controller.PauseAfterSeeking : Controller.PlayAfterSeeking);
+ this.video.play();
+ this.video.playbackRate = this.nextRate() * -1;
+ this.seekInterval = setInterval(this.seekBackFaster.bind(this), this.SeekDelay);
+ },
+
+ seekBackFaster: function()
+ {
+ this.video.playbackRate = this.nextRate() * -1;
+ },
+
+ handleSeekBackMouseUp: function(event)
+ {
+ this.video.playbackRate = this.video.defaultPlaybackRate;
+ if (this.actionAfterSeeking === Controller.PauseAfterSeeking)
+ this.video.pause();
+ else if (this.actionAfterSeeking === Controller.PlayAfterSeeking)
+ this.video.play();
+ if (this.seekInterval)
+ clearInterval(this.seekInterval);
+ },
+
+ handleSeekForwardMouseDown: function(event)
+ {
+ this.actionAfterSeeking = (this.canPlay() ? Controller.PauseAfterSeeking : Controller.PlayAfterSeeking);
+ this.video.play();
+ this.video.playbackRate = this.nextRate();
+ this.seekInterval = setInterval(this.seekForwardFaster.bind(this), this.SeekDelay);
+ },
+
+ seekForwardFaster: function()
+ {
+ this.video.playbackRate = this.nextRate();
+ },
+
+ handleSeekForwardMouseUp: function(event)
+ {
+ this.video.playbackRate = this.video.defaultPlaybackRate;
+ if (this.actionAfterSeeking === Controller.PauseAfterSeeking)
+ this.video.pause();
+ else if (this.actionAfterSeeking === Controller.PlayAfterSeeking)
+ this.video.play();
+ if (this.seekInterval)
+ clearInterval(this.seekInterval);
+ },
+
+ updateDuration: function()
+ {
+ var duration = this.video.duration;
+ this.controls.timeline.min = 0;
+ this.controls.timeline.max = duration;
+
+ this.setIsLive(duration === Number.POSITIVE_INFINITY);
+
+ this.controls.currentTime.classList.toggle(this.ClassNames.hourLongTime, duration >= 60*60);
+ this.controls.remainingTime.classList.toggle(this.ClassNames.hourLongTime, duration >= 60*60);
+ },
+
+ progressFillStyle: function(context)
+ {
+ var height = this.timelineHeight;
+ var gradient = context.createLinearGradient(0, 0, 0, height);
+ gradient.addColorStop(0, 'rgb(2, 2, 2)');
+ gradient.addColorStop(1, 'rgb(23, 23, 23)');
+ return gradient;
+ },
+
+ updateProgress: function(forceUpdate)
+ {
+ if (!forceUpdate && this.controlsAreHidden())
+ return;
+
+ this.updateTimelineMetricsIfNeeded();
+
+ var width = this.timelineWidth;
+ var height = this.timelineHeight;
+
+ var context = document.getCSSCanvasContext('2d', 'timeline-' + this.timelineID, width, height);
+ context.clearRect(0, 0, width, height);
+
+ context.fillStyle = this.progressFillStyle(context);
+
+ var duration = this.video.duration;
+ var buffered = this.video.buffered;
+ for (var i = 0, end = buffered.length; i < end; ++i) {
+ var startTime = buffered.start(i);
+ var endTime = buffered.end(i);
+
+ var startX = width * startTime / duration;
+ var endX = width * endTime / duration;
+ context.fillRect(startX, 0, endX - startX, height);
+ }
+ },
+
+ formatTime: function(time)
+ {
+ if (isNaN(time))
+ time = 0;
+ var absTime = Math.abs(time);
+ var intSeconds = Math.floor(absTime % 60).toFixed(0);
+ var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
+ var intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
+ var sign = time < 0 ? '-' : String();
+
+ if (intHours > 0)
+ return sign + intHours + ':' + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2);
+
+ return sign + String('00' + intMinutes).slice(-2) + ":" + String('00' + intSeconds).slice(-2)
+ },
+
+ updatePlaying: function()
+ {
+ this.setPlaying(!this.canPlay());
+ },
+
+ setPlaying: function(isPlaying)
+ {
+ if (this.isPlaying === isPlaying)
+ return;
+ this.isPlaying = isPlaying;
+
+ if (!isPlaying) {
+ this.controls.panel.classList.add(this.ClassNames.paused);
+ this.controls.playButton.classList.add(this.ClassNames.paused);
+ this.controls.playButton.setAttribute('aria-label', this.UIString('Play'));
+ this.showControls();
+ } else {
+ this.controls.panel.classList.remove(this.ClassNames.paused);
+ this.controls.playButton.classList.remove(this.ClassNames.paused);
+ this.controls.playButton.setAttribute('aria-label', this.UIString('Pause'));
+
+ this.hideControls();
+ this.resetHideControlsTimer();
+ }
+ },
+
+ showControls: function()
+ {
+ this.controls.panel.classList.add(this.ClassNames.show);
+ this.controls.panel.classList.remove(this.ClassNames.hidden);
+
+ this.updateTime();
+ this.setNeedsTimelineMetricsUpdate();
+ },
+
+ hideControls: function()
+ {
+ this.controls.panel.classList.remove(this.ClassNames.show);
+ },
+
+ controlsAreAlwaysVisible: function()
+ {
+ return this.controls.panel.classList.contains(this.ClassNames.noVideo);
+ },
+
+ controlsAreHidden: function()
+ {
+ if (this.controlsAreAlwaysVisible())
+ return false;
+
+ var panel = this.controls.panel;
+ return (!panel.classList.contains(this.ClassNames.show) || panel.classList.contains(this.ClassNames.hidden))
+ && (panel.parentElement.querySelector(':hover') !== panel);
+ },
+
+ removeControls: function()
+ {
+ if (this.controls.panel.parentNode)
+ this.controls.panel.parentNode.removeChild(this.controls.panel);
+ this.destroyCaptionMenu();
+ },
+
+ addControls: function()
+ {
+ this.base.appendChild(this.controls.panelCompositedParent);
+ this.controls.panelCompositedParent.appendChild(this.controls.panel);
+ this.setNeedsTimelineMetricsUpdate();
+ },
+
+ updateTime: function(forceUpdate)
+ {
+ if (!forceUpdate && this.controlsAreHidden())
+ return;
+
+ var currentTime = this.video.currentTime;
+ var timeRemaining = currentTime - this.video.duration;
+ this.controls.currentTime.innerText = this.formatTime(currentTime);
+ this.controls.timeline.value = this.video.currentTime;
+ this.controls.remainingTime.innerText = this.formatTime(timeRemaining);
+ },
+
+ updateReadyState: function()
+ {
+ this.updateStatusDisplay();
+ },
+
+ setStatusHidden: function(hidden)
+ {
+ if (this.statusHidden === hidden)
+ return;
+
+ this.statusHidden = hidden;
+
+ if (hidden) {
+ this.controls.statusDisplay.classList.add(this.ClassNames.hidden);
+ this.controls.currentTime.classList.remove(this.ClassNames.hidden);
+ this.controls.timeline.classList.remove(this.ClassNames.hidden);
+ this.controls.remainingTime.classList.remove(this.ClassNames.hidden);
+ this.setNeedsTimelineMetricsUpdate();
+ } else {
+ this.controls.statusDisplay.classList.remove(this.ClassNames.hidden);
+ this.controls.currentTime.classList.add(this.ClassNames.hidden);
+ this.controls.timeline.classList.add(this.ClassNames.hidden);
+ this.controls.remainingTime.classList.add(this.ClassNames.hidden);
+ }
+ },
+
+ trackHasThumbnails: function(track)
+ {
+ return track.kind === 'thumbnails' || (track.kind === 'metadata' && track.label === 'thumbnails');
+ },
+
+ updateThumbnail: function()
+ {
+ for (var i = 0; i < this.video.textTracks.length; ++i) {
+ var track = this.video.textTracks[i];
+ if (this.trackHasThumbnails(track)) {
+ this.controls.thumbnail.classList.remove(this.ClassNames.hidden);
+ return;
+ }
+ }
+
+ this.controls.thumbnail.classList.add(this.ClassNames.hidden);
+ },
+
+ updateCaptionButton: function()
+ {
+ if (this.video.webkitHasClosedCaptions)
+ this.controls.captionButton.classList.remove(this.ClassNames.hidden);
+ else
+ this.controls.captionButton.classList.add(this.ClassNames.hidden);
+ },
+
+ updateCaptionContainer: function()
+ {
+ if (!this.host.textTrackContainer)
+ return;
+
+ var hasClosedCaptions = this.video.webkitHasClosedCaptions;
+ var hasHiddenClass = this.host.textTrackContainer.classList.contains(this.ClassNames.hidden);
+
+ if (hasClosedCaptions && hasHiddenClass)
+ this.host.textTrackContainer.classList.remove(this.ClassNames.hidden);
+ else if (!hasClosedCaptions && !hasHiddenClass)
+ this.host.textTrackContainer.classList.add(this.ClassNames.hidden);
+
+ this.updateBase();
+ this.host.updateTextTrackContainer();
+ },
+
+ buildCaptionMenu: function()
+ {
+ var tracks = this.host.sortedTrackListForMenu(this.video.textTracks);
+ if (!tracks || !tracks.length)
+ return;
+
+ this.captionMenu = document.createElement('div');
+ this.captionMenu.setAttribute('pseudo', '-webkit-media-controls-closed-captions-container');
+ this.captionMenu.setAttribute('id', 'audioTrackMenu');
+ this.base.appendChild(this.captionMenu);
+ this.captionMenuItems = [];
+
+ var offItem = this.host.captionMenuOffItem;
+ var automaticItem = this.host.captionMenuAutomaticItem;
+ var displayMode = this.host.captionDisplayMode;
+
+ var list = document.createElement('div');
+ this.captionMenu.appendChild(list);
+ list.classList.add(this.ClassNames.list);
+
+ var heading = document.createElement('h3');
+ heading.id = 'webkitMediaControlsClosedCaptionsHeading'; // for AX menu label
+ list.appendChild(heading);
+ heading.innerText = this.UIString('Subtitles');
+
+ var ul = document.createElement('ul');
+ ul.setAttribute('role', 'menu');
+ ul.setAttribute('aria-labelledby', 'webkitMediaControlsClosedCaptionsHeading');
+ list.appendChild(ul);
+
+ for (var i = 0; i < tracks.length; ++i) {
+ var menuItem = document.createElement('li');
+ menuItem.setAttribute('role', 'menuitemradio');
+ menuItem.setAttribute('tabindex', '-1');
+ this.captionMenuItems.push(menuItem);
+ this.listenFor(menuItem, 'click', this.captionItemSelected);
+ this.listenFor(menuItem, 'keyup', this.handleCaptionItemKeyUp);
+ ul.appendChild(menuItem);
+
+ var track = tracks[i];
+ menuItem.innerText = this.host.displayNameForTrack(track);
+ menuItem.track = track;
+
+ if (track === offItem) {
+ var offMenu = menuItem;
+ continue;
+ }
+
+ if (track === automaticItem) {
+ if (displayMode === 'automatic') {
+ menuItem.classList.add(this.ClassNames.selected);
+ menuItem.setAttribute('tabindex', '0');
+ menuItem.setAttribute('aria-checked', 'true');
+ }
+ continue;
+ }
+
+ if (displayMode != 'automatic' && track.mode === 'showing') {
+ var trackMenuItemSelected = true;
+ menuItem.classList.add(this.ClassNames.selected);
+ menuItem.setAttribute('tabindex', '0');
+ menuItem.setAttribute('aria-checked', 'true');
+ }
+
+ }
+
+ if (offMenu && displayMode === 'forced-only' && !trackMenuItemSelected) {
+ offMenu.classList.add(this.ClassNames.selected);
+ menuItem.setAttribute('tabindex', '0');
+ menuItem.setAttribute('aria-checked', 'true');
+ }
+
+ // focus first selected menuitem
+ for (var i = 0, c = this.captionMenuItems.length; i < c; i++) {
+ var item = this.captionMenuItems[i];
+ if (item.classList.contains(this.ClassNames.selected)) {
+ item.focus();
+ break;
+ }
+ }
+
+ },
+
+ captionItemSelected: function(event)
+ {
+ this.host.setSelectedTextTrack(event.target.track);
+ this.destroyCaptionMenu();
+ },
+
+ focusSiblingCaptionItem: function(event)
+ {
+ var currentItem = event.target;
+ var pendingItem = false;
+ switch(event.keyCode) {
+ case this.KeyCodes.left:
+ case this.KeyCodes.up:
+ pendingItem = currentItem.previousSibling;
+ break;
+ case this.KeyCodes.right:
+ case this.KeyCodes.down:
+ pendingItem = currentItem.nextSibling;
+ break;
+ }
+ if (pendingItem) {
+ currentItem.setAttribute('tabindex', '-1');
+ pendingItem.setAttribute('tabindex', '0');
+ pendingItem.focus();
+ }
+ },
+
+ handleCaptionItemKeyUp: function(event)
+ {
+ switch (event.keyCode) {
+ case this.KeyCodes.enter:
+ case this.KeyCodes.space:
+ this.captionItemSelected(event);
+ break;
+ case this.KeyCodes.escape:
+ this.destroyCaptionMenu();
+ break;
+ case this.KeyCodes.left:
+ case this.KeyCodes.up:
+ case this.KeyCodes.right:
+ case this.KeyCodes.down:
+ this.focusSiblingCaptionItem(event);
+ break;
+ default:
+ return;
+ }
+ // handled
+ event.stopPropagation();
+ event.preventDefault();
+ },
+
+ destroyCaptionMenu: function()
+ {
+ if (!this.captionMenu)
+ return;
+
+ this.captionMenuItems.forEach(function(item){
+ this.stopListeningFor(item, 'click', this.captionItemSelected);
+ this.stopListeningFor(item, 'keyup', this.handleCaptionItemKeyUp);
+ }, this);
+
+ // FKA and AX: focus the trigger before destroying the element with focus
+ if (this.controls.captionButton)
+ this.controls.captionButton.focus();
+
+ if (this.captionMenu.parentNode)
+ this.captionMenu.parentNode.removeChild(this.captionMenu);
+ delete this.captionMenu;
+ delete this.captionMenuItems;
+ },
+
+ updateHasAudio: function()
+ {
+ if (this.video.audioTracks.length)
+ this.controls.muteBox.classList.remove(this.ClassNames.hidden);
+ else
+ this.controls.muteBox.classList.add(this.ClassNames.hidden);
+ },
+
+ updateHasVideo: function()
+ {
+ if (this.video.videoTracks.length)
+ this.controls.panel.classList.remove(this.ClassNames.noVideo);
+ else
+ this.controls.panel.classList.add(this.ClassNames.noVideo);
+ },
+
+ updateVolume: function()
+ {
+ if (this.video.muted || !this.video.volume) {
+ this.controls.muteButton.classList.add(this.ClassNames.muted);
+ this.controls.volume.value = 0;
+ } else {
+ this.controls.muteButton.classList.remove(this.ClassNames.muted);
+ this.controls.volume.value = this.video.volume;
+ }
+ },
+
+ isAudio: function()
+ {
+ return this.video instanceof HTMLAudioElement;
+ },
+
+ clearHideControlsTimer: function()
+ {
+ if (this.hideTimer)
+ clearTimeout(this.hideTimer);
+ this.hideTimer = null;
+ },
+
+ resetHideControlsTimer: function()
+ {
+ if (this.hideTimer)
+ clearTimeout(this.hideTimer);
+ this.hideTimer = setTimeout(this.hideControls.bind(this), this.HideControlsDelay);
+ },
+};
diff --git a/Source/WebCore/Modules/mediacontrols/mediaControlsGtk.js b/Source/WebCore/Modules/mediacontrols/mediaControlsGtk.js
new file mode 100644
index 000000000..bca7776c8
--- /dev/null
+++ b/Source/WebCore/Modules/mediacontrols/mediaControlsGtk.js
@@ -0,0 +1,258 @@
+function createControls(root, video, host)
+{
+ return new ControllerGtk(root, video, host);
+};
+
+function ControllerGtk(root, video, host)
+{
+ Controller.call(this, root, video, host);
+};
+
+function contains(list, obj)
+{
+ var i = list.length;
+ while (i--)
+ if (list[i] === obj)
+ return true;
+ return false;
+};
+
+ControllerGtk.prototype = {
+
+ createControls: function()
+ {
+ Controller.prototype.createControls.apply(this);
+
+ this.controls.currentTime.classList.add(this.ClassNames.hidden);
+ this.controls.remainingTime.classList.add(this.ClassNames.hidden);
+
+ this.controls.volumeBox.classList.add(this.ClassNames.hiding);
+
+ this.listenFor(this.controls.muteBox, 'mouseout', this.handleVolumeBoxMouseOut);
+ this.listenFor(this.controls.muteButton, 'mouseover', this.handleMuteButtonMouseOver);
+ this.listenFor(this.controls.volumeBox, 'mouseover', this.handleMuteButtonMouseOver);
+ this.listenFor(this.controls.volume, 'mouseover', this.handleMuteButtonMouseOver);
+ this.listenFor(this.controls.captionButton, 'mouseover', this.handleCaptionButtonMouseOver);
+ this.listenFor(this.controls.captionButton, 'mouseout', this.handleCaptionButtonMouseOut);
+
+ var enclosure = this.controls.enclosure = document.createElement('div');
+ enclosure.setAttribute('pseudo', '-webkit-media-controls-enclosure');
+ },
+
+ configureInlineControls: function()
+ {
+ this.controls.panel.appendChild(this.controls.playButton);
+ this.controls.panel.appendChild(this.controls.timeline);
+ this.controls.panel.appendChild(this.controls.currentTime);
+ this.controls.panel.appendChild(this.controls.remainingTime);
+ this.controls.panel.appendChild(this.controls.captionButton);
+ this.controls.panel.appendChild(this.controls.fullscreenButton);
+ this.controls.panel.appendChild(this.controls.muteBox);
+ this.controls.muteBox.appendChild(this.controls.muteButton);
+ this.controls.muteBox.appendChild(this.controls.volumeBox);
+ this.controls.volumeBox.appendChild(this.controls.volume);
+ this.controls.enclosure.appendChild(this.controls.panel);
+ },
+
+ shouldHaveControls: function()
+ {
+ return this.video.controls || this.isFullScreen();
+ },
+
+ reconnectControls: function()
+ {
+ Controller.prototype.disconnectControls.apply(this, arguments);
+
+ this.configureInlineControls();
+
+ if (this.shouldHaveControls())
+ this.addControls();
+ },
+
+ setStatusHidden: function(hidden)
+ {
+ },
+
+ updateTime: function(forceUpdate)
+ {
+ if (!forceUpdate && this.controlsAreHidden())
+ return;
+
+ var currentTime = this.video.currentTime;
+ var duration = this.video.duration;
+
+ this.controls.currentTime.innerText = this.formatTime(currentTime);
+ this.controls.timeline.value = currentTime;
+ if (duration === Infinity || duration === NaN)
+ this.controls.remainingTime.classList.add(this.ClassNames.hidden);
+ else {
+ this.controls.currentTime.innerText += " / " + this.formatTime(duration);
+ this.controls.remainingTime.innerText = this.formatTime(duration);
+ if (this.controls.currentTime.classList.contains(this.ClassNames.hidden))
+ this.controls.remainingTime.classList.remove(this.ClassNames.hidden);
+ }
+
+ if (currentTime > 0)
+ this.showCurrentTime();
+ },
+
+ showCurrentTime: function()
+ {
+ this.controls.currentTime.classList.remove(this.ClassNames.hidden);
+ this.controls.remainingTime.classList.add(this.ClassNames.hidden);
+ },
+
+ handlePlay: function(event)
+ {
+ Controller.prototype.handlePlay.apply(this, arguments);
+ this.showCurrentTime();
+ if (!this.isLive)
+ this.showCurrentTime();
+ },
+
+ handleTimeUpdate: function(event)
+ {
+ this.updateTime();
+ },
+
+ handleMuteButtonMouseOver: function(event)
+ {
+ if (this.video.offsetTop + this.controls.enclosure.offsetTop < 105) {
+ this.controls.volumeBox.classList.add(this.ClassNames.down);
+ this.controls.panel.classList.add(this.ClassNames.down);
+ } else {
+ this.controls.volumeBox.classList.remove(this.ClassNames.down);
+ this.controls.panel.classList.remove(this.ClassNames.down);
+ }
+ this.controls.volumeBox.classList.remove(this.ClassNames.hiding);
+ return true;
+ },
+
+ handleVolumeBoxMouseOut: function(event)
+ {
+ this.controls.volumeBox.classList.add(this.ClassNames.hiding);
+ return true;
+ },
+
+ removeControls: function()
+ {
+ if (this.controls.enclosure.parentNode)
+ this.controls.enclosure.parentNode.removeChild(this.controls.enclosure);
+ this.destroyCaptionMenu();
+ },
+
+ addControls: function()
+ {
+ this.base.appendChild(this.controls.enclosure);
+ },
+
+ updateReadyState: function()
+ {
+ if (this.host.supportsFullscreen && this.video.videoTracks.length)
+ this.controls.fullscreenButton.classList.remove(this.ClassNames.hidden);
+ else
+ this.controls.fullscreenButton.classList.add(this.ClassNames.hidden);
+ this.updateVolume();
+ },
+
+ updateDuration: function()
+ {
+ Controller.prototype.updateDuration.apply(this, arguments);
+ if (this.isLive)
+ this.controls.timeline.max = 0;
+ },
+
+ setIsLive: function(live)
+ {
+ Controller.prototype.setIsLive.apply(this, arguments);
+ this.controls.timeline.disabled = this.isLive;
+ },
+
+ updatePlaying: function()
+ {
+ Controller.prototype.updatePlaying.apply(this, arguments);
+ if (!this.canPlay())
+ this.showControls();
+ },
+
+ handleCaptionButtonClicked: function(event)
+ {
+ this.handleCaptionButtonShowMenu(event)
+ return true;
+ },
+
+ buildCaptionMenu: function()
+ {
+ Controller.prototype.buildCaptionMenu.apply(this, arguments);
+
+ this.listenFor(this.captionMenu, 'mouseout', this.handleCaptionMouseOut);
+ this.listenFor(this.captionMenu, 'transitionend', this.captionMenuTransitionEnd);
+
+ this.captionMenu.captionMenuTreeElements = this.captionMenu.getElementsByTagName("*");
+
+ // Caption menu has to be centered to the caption button.
+ var captionButtonCenter = this.controls.panel.offsetLeft + this.controls.captionButton.offsetLeft +
+ this.controls.captionButton.offsetWidth / 2;
+ var captionMenuLeft = (captionButtonCenter - this.captionMenu.offsetWidth / 2);
+ if (captionMenuLeft + this.captionMenu.offsetWidth > this.controls.panel.offsetLeft + this.controls.panel.offsetWidth)
+ this.captionMenu.classList.add(this.ClassNames.out);
+ this.captionMenu.style.left = captionMenuLeft + 'px';
+ // As height is not in the css, it needs to be specified to animate it.
+ this.captionMenu.height = this.captionMenu.offsetHeight;
+ this.captionMenu.style.height = 0;
+ },
+
+ destroyCaptionMenu: function()
+ {
+ this.hideCaptionMenu();
+ },
+
+ showCaptionMenu: function()
+ {
+ this.captionMenu.style.height = this.captionMenu.height + 'px';
+ },
+
+ hideCaptionMenu: function()
+ {
+ this.captionMenu.style.height = 0;
+ },
+
+ captionMenuTransitionEnd: function(event)
+ {
+ if (this.captionMenu.offsetHeight === 0)
+ Controller.prototype.destroyCaptionMenu.apply(this, arguments);
+ },
+
+ handleCaptionButtonMouseOver: function(event)
+ {
+ this.handleCaptionButtonShowMenu(event);
+ return true;
+ },
+
+ handleCaptionButtonShowMenu: function(event)
+ {
+ if (!this.captionMenu)
+ this.buildCaptionMenu();
+ if (!contains(this.captionMenu.captionMenuTreeElements, event.relatedTarget))
+ this.showCaptionMenu();
+ return true;
+ },
+
+ handleCaptionButtonMouseOut: function(event)
+ {
+ if (this.captionMenu && !contains(this.captionMenu.captionMenuTreeElements, event.relatedTarget))
+ this.hideCaptionMenu();
+ return true;
+ },
+
+ handleCaptionMouseOut: function(event)
+ {
+ if (event.relatedTarget != this.controls.captionButton &&
+ !contains(this.captionMenu.captionMenuTreeElements, event.relatedTarget))
+ this.hideCaptionMenu();
+ return true;
+ },
+};
+
+Object.create(Controller.prototype).extend(ControllerGtk.prototype);
+Object.defineProperty(ControllerGtk.prototype, 'constructor', { enumerable:false, value:ControllerGtk });
diff --git a/Source/WebCore/Modules/mediacontrols/mediaControlsiOS.css b/Source/WebCore/Modules/mediacontrols/mediaControlsiOS.css
new file mode 100644
index 000000000..28801ef86
--- /dev/null
+++ b/Source/WebCore/Modules/mediacontrols/mediaControlsiOS.css
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2012, 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.
+ */
+
+/* You'll see a lot of !important rules in this file. This is because
+ the inheritance and specificity of Shadow DOM trees is slightly
+ tricky. The page might have accidentally set a style and we have
+ to make sure it is reset. */
+
+audio {
+ min-width: 260px;
+ height: 39px;
+}
+
+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;
+}
+
+::-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-family: -apple-system;
+ overflow: hidden;
+}
+
+video::-webkit-media-controls-panel input[type="button"],
+audio::-webkit-media-controls-panel input[type="button"],
+video::-webkit-media-controls-panel button,
+audio::-webkit-media-controls-panel button {
+ padding: 0;
+ border: none;
+ -webkit-appearance: none;
+}
+
+video::-webkit-media-controls-inline-playback-placeholder,
+audio::-webkit-media-controls-inline-playback-placeholder {
+ display: block;
+ z-index: 0;
+ width: 100%;
+ height: 100%;
+ background-color: black;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ background-size: 100% 50%;
+}
+
+video::-webkit-media-controls-inline-playback-placeholder.hidden,
+audio::-webkit-media-controls-inline-playback-placeholder.hidden {
+ display: none;
+}
+
+video::-webkit-media-text-track-container,
+audio::-webkit-media-text-track-container {
+ position: relative;
+ -webkit-flex: 1 1 auto;
+}
+
+video::-webkit-media-controls-panel-container {
+ -webkit-transform: translateZ(0);
+ width: 100%;
+ direction: ltr;
+ height: 50px;
+ position: absolute;
+ bottom: 0;
+ pointer-events: none;
+}
+
+audio::-webkit-media-controls-panel-container {
+ width: 100%;
+ direction: ltr;
+ height: 39px;
+ position: absolute;
+ bottom: 0;
+}
+
+video::-webkit-media-controls-panel-background {
+ -webkit-transform: translateZ(0);
+ width: 101%; /* Due to some rounding issues we make this a little bit wider than should be necessary. */
+ height: 51px; /* And taller. */
+ -webkit-appearance: media-controls-light-bar-background;
+ transition: opacity 0.25s linear;
+ opacity: 0;
+ pointer-events: none;
+ bottom: 0;
+ position: absolute;
+}
+
+audio::-webkit-media-controls-panel-background {
+ display: none;
+}
+
+video::-webkit-media-controls-panel-background.show,
+video::-webkit-media-controls-panel-background.paused {
+ opacity: 1;
+}
+
+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;
+}
+
+audio::-webkit-media-controls-panel,
+video::-webkit-media-controls-panel {
+ box-sizing: border-box;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ padding: 0;
+
+ -webkit-user-select: none;
+
+ display: -webkit-flex;
+ -webkit-flex-direction: row;
+ -webkit-flex-wrap: nowrap;
+ -webkit-justify-content: flex-start;
+ -webkit-align-items: center;
+
+ transition: opacity 0.25s linear;
+ -webkit-transform-origin: bottom left;
+}
+
+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;
+}
+
+video::-webkit-media-controls-panel {
+ height: 50px;
+ opacity: 0;
+ pointer-events: none;
+ -webkit-transform: translate3d(0, 0, 0);
+}
+
+audio::-webkit-media-controls-panel {
+ height: 39px;
+ background-color: rgba(212, 212, 212, 1);
+}
+
+video::-webkit-media-controls-panel.show,
+video::-webkit-media-controls-panel.paused {
+ pointer-events: auto;
+ opacity: 1;
+}
+
+video::-webkit-media-controls-panel.picture-in-picture {
+ pointer-events: none;
+}
+
+video::-webkit-media-controls-rewind-button,
+audio::-webkit-media-controls-rewind-button,
+video::-webkit-media-controls-panel .mute-box,
+audio::-webkit-media-controls-panel .mute-box,
+video::-webkit-media-controls-mute-button,
+audio::-webkit-media-controls-mute-button,
+video::-webkit-media-controls-volume-max-button,
+video::-webkit-media-controls-panel .volume-box,
+audio::-webkit-media-controls-panel .volume-box,
+audio::-webkit-media-controls-volume-slider,
+video::-webkit-media-controls-volume-slider {
+ display: none !important;
+}
+
+video::-webkit-media-controls-start-playback-button {
+ -webkit-appearance: none;
+ position: absolute;
+ width: 72px;
+ height: 72px;
+ left: calc(50% - 36px);
+ top: calc(50% - 36px);
+ -webkit-transform: translate3d(0, 0, 0);
+}
+
+video::-webkit-media-controls-start-playback-background {
+ -webkit-appearance: none;
+ position: absolute;
+ width: 72px;
+ height: 72px;
+ -webkit-appearance: media-controls-light-bar-background;
+ -webkit-transform: translateZ(0);
+ -webkit-clip-path: circle(36px);
+}
+
+video::-webkit-media-controls-start-playback-button .webkit-media-controls-start-playback-glyph {
+ -webkit-appearance: none;
+ position: absolute;
+ width: 72px;
+ height: 72px;
+ opacity: 0.6;
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAACMCAYAAACuwEE+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABKFJREFUeNrsnV9ojWEcx5+jhdUulF0YNS6IpDRJuNDhyi5cjBvkglGKCxtKIlxILkwi+VPsRnbDphTuLEXzJ8u/0UhstJupKRojx+/X85xIztlztrNznve8n099W9lZ3vd9Pr3P8z7P7zxvIpVKGQBfxnEJAGEAYQBhAGEAYQAQBhAGEAYQBhAGEAYAYQBhAGEAYQBhAGEAEAYQBhAGEAYQBgBhAGEAYQBhAGEAYQAQBhAGEAaCpCyKB51IJPTHRMkVyQpJr+Sa5IzkXZwasOAbQul/GLU4GvTw/8kPSZNkUpyEKWSi3CWtynDH3Cl5Kamny2UM8zezs/xuiuSC5JEkSTMjjDLV4zM1ktuSFkk1zR1fYSbo2DeHz6+VvJIcllTQ7PET5vsI/qZcss+Jsy5H4SDG8zDTJJcl9ySLUQBhfFnspGmWVKECwvig3dJGSbfrrspRAmF8qHAD4i5JHZcDYXyZIWl1j+I1XA6E8SVp7KTfWUkllwNhfK/NVslryS7JeC4JwvigC5nHJM8lKxEGfJkluekyF2HAF73LPJGcMDEqo0CY0aFlFDvc+GabiWghGsIUHn2COu3uOEmEAV90TKNzNzqHMxNhwBedJX4hOWpKtIwCYfKPztfsMXZ9quTKRBFm7NAV8HSZ6BKEAV90TequsTU41QgDPmgZhVb5abXfIRPhMgqEKSwqykET4TJRhCkO1eZPmegChAFftEz0oRscVyEM+LZBvXsM32sCL6NAmHDQib4jxk781SEM+KJLC+ky0XkIA74kJZ3GLm5WIgz4oGUTWj6hZRSNJoAyCoSJBlqodVzyTLIGYcCXOcbuunXD2C1NEAa8qJXcl0xHGPBFZ4svIgzkwlKEgVy4ijDgy2PJdoSB4dDtZXWxcpnkM8JANh4YOwO8RfKVx2rIRJ9kk7HlEHeKeSBltEXQDBo7w6tfW/kSwgEhTLi0SXZL3oZ0UAgTHrpCrdvft4d4cIxhwqHfPSYvClUW7jBhoG9gOWnspowDoR8swhSXW8Zuh9YVlQNGmOKg30tqdMJECsYwhWXAPfnMj6Is3GEKxy/JOckBN7iNLAgz9rS7x+TOUjgZhBk73rtxSlspnRTC5B+dwtepfJ3SHyy1k0OY/KFlB5eM3X2qr1RPEmHyQ4frfjpK/UR5rB4deifZYGxtbUccTpg7zMgIruwAYcKl1XU/PXE8ebokf3QeZbmxX1XtietFQJjh0ZlZfW/SQhNw2QFdUvEZkpwyESk7QJjioguD+raSbi4FXVI2tC6l1gVZECYj2uVoIVNkyw7okgrDT8l5Yzdb7kcHhMmGPvE0GPtyLKBLysgbyWpj51SQhTtMRtJlB02SbzR9/IT5JJns8TktO2iW7DclXHZAlzQ8Po2vK8i6krwZWRDmaZbffZCsNzEqO0CY4bn+n38bcmMUfctri+uOII8kUqnoXdNEIpEef6kUOiv70dhia91mvTdODVjo9oukMECXBAgDCAMIwyUAhAGEAYQBhAGEAUAYQBhAGEAYQBhAGACEAYQBhAGEAYQBhAFAGEAYQBhAGEAYAIQBhAGEAYQBhIEY8FuAAQBHs44NYWX3+AAAAABJRU5ErkJggg==');
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ background-size: 100% 100%;
+ /* FIXME This is an ad-hoc fix until we fix the real compositing issue tracked by webkit.org/b/158147 */
+ will-change: opacity;
+}
+
+video::-webkit-media-controls-start-playback-button .webkit-media-controls-start-playback-glyph.active {
+ opacity: 1;
+}
+
+video::-webkit-media-controls-start-playback-button .webkit-media-controls-start-playback-glyph.failed {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIwAAACMCAYAAACuwEE+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABspJREFUeNrsnVvIVUUYhr8tZv5UZGCShdZFeBElJAQWVD9B5EVQEVIhQRR0URdZQXSAMjIoSqikqLCisIQO1kUHo8woKERMQjqoRUVlB43sfDD7m489izbimjXf3rPWnrV9XnhvF5s9z/rmm5l3rdWZmJgQhGLVARgEMAhgEMAggEEAgxDAIIBBAIMABgEMAhiEAAYBDAIYBDAIYBDAIAQwCGAQwCCAQQCDEMAggEEAgwAGAQwCGIQABgEMAhiUpxSYulyzpjq/6Py78xbnO52PYUTrHd82A7NY/4+9vNt5mfM0UGkvMIc7L3d+y/kh52MT/Rdr9wFM4W+cL3WeBDDtAmbM+eO9BvMv57sTVIGvAsAUfs95HGDaA8wFgcH8dsAq8G8EMIVXOc8GmPyBuTKyCpxu/B8ONMBSWJvjpc4HA0y+wMxx/jtiMLVaPGWsAhN9Wqeyi3RLAWDybHov9ne3pQqM1QhM4Xed5wNMnsvq2b6CxPYdX0RUgR8TQKO/5zHnmQCT5z7MfN+zWKrAvJL/YobzI857EoDzi/NNkZUNYBreuJvkV0fbIwdzjwejrAqc6LwuATTqz5zPA5g8d3p1tXKX35eJrQLXOU8puZ4O9LZE4KzzIAJMhkcDuvO72jCY2wJVQGG60cM1KDRa2R50ng4weZ4lnen8gWFAX3M+ruRaMxP2N9pcXxuobAAzxMPHyc5XOO+IHEw9bLw/UAVS9jdbnRcATJ6n1dM9CLsjB/MHD9rkkust9Ev1FOC8EqhsADPkeIMOzKuGwdQp7aySa435pXOK/kZBvkdaEKPYX/Mw1hXQaimPUaTsb3ZUVDaAGWKAaopvPmMrRBGjOKTkevP8xmCKaUor2zjA5Jm4s1aI7VIeo+j4I4hU/U2osgHMkCOaugJ6xzCYeiRxSgP9jVa2OySTGAXADFYhqmIUWr1Wii2c1U9lA5ghAdNbIW4VW4zilsBh4/yE/Y1WtpMBJi9gCqWMUaTsb/oJiAFMYoXCT6eKLUbxduCwUavQEkP1qqpsS6TBGAXA/K+quzZ1jMJavQYNiAFM6v+i5669OXDXHur3Y2JjFD9VHDam7G9CATGAqQmY2LtW90deEFuM4tzA6myRoXoNUtkApiZgYsPdC8QWowgdNupey9JE/Y3uAd0giWMUAFMNTLEqeTxw1xYxitggeRGjOKyB/iYUEAOYmoCJDXdbYxRVh41a2TZKupjo8QDTLDCx/Y1OOW8YDxvPSLQ6GyQgBjA1AROz35I6RpGyv9Gp8+p+YhQAMxgwMasSbTqvN8YoQoeN2t88nWia+sj5fIBpFpjY/iZljEI1btx9Dvll5yMApllgCn9asSo5ybhRtyGwrE/Z32hfdjTANA9MzMNrqWMUOn3dbth9LvNagBkeMDH9zUHGRva3imnP+hDf3v4DYIYLTOFdFedJWjlW9bGsl8T9zUqAyQOY2F1X60CHpr2iv/ku8lob/cEqwGQEjGWgvzdOezNKrjet4nRd+6MVfnpklZQpMDG7rlUDbX1me1/9zXrn09iHaQcwloF+yTjtnV0x7b3ufEk/gSuAGT4wvQN9TkMxCg4fRwCYmIHWs5+rjDGKeyXhM9sAkx8wMf1N6hgFwLQcmN7+5prAQM8V2/tqNsuAz2wDTN7A9OZlFjQUowCYEQAmpr+ZKrbnuf+UPl59DzDtAqa3v5kWiFE8KvF5YNMz2wDTPmB6G9nLAgNtfd4p6tX3ANNOYPQ44AGpzuYWMYqvjTGKowBmdIDp5yXR1jxwaXoQYNoDzOcy+PNFGqN41hijWAgw7QKmjg9dWGMU+sjMLIDJGxjtJ56Q+j6lo83y5RL/0utN4r9LDjD5AdPkx7osMYq5AJMXMLonskiG8znAOX5jMPT7TgCYPIDJ6YOjZTGKDUxJeQDznOT3SeMDpPsorZ5P/ezPno6so+nt1DmwnU6nbmCa1CZ/Ev2mtEipxxdgqrXTL5NX+JWQAAzA7Ev6ve3lvlfZJS0VwDQDzBrpRiu3SssFMPUC86F0nwhYIyMigKkHGJ1ybnO+z/kfGSEBTFpgFI6HpfvdgZ0yggKYdMDo8nix8/sywgKYwYH5RLofTH9e9gMBTP/A/Crdd9Etk26gWgCmzwu29Ghgp8THDmp7NXsbgOEsqavNklfsAGAyB+bJAChfSkOflwGY9gBzodg/PQww+zEw+vzyM76Z3eIb2lkgUi8wnQYGFo2QAAYBDAIYBDAIYBDAIAQwCGAQwCCAQQCDAAYhgEEAgwAGAQwCGAQwCAEMAhgEMAhgEMAgBDAIYBDAIIBBAIMABiGAQQCDGtN/AgwAK0XUGzgj/EoAAAAASUVORK5CYII=');
+}
+
+/* ================== ALL INLINE BUTTONS ====================== */
+
+video::-webkit-media-controls-play-button,
+audio::-webkit-media-controls-play-button,
+video::-webkit-media-controls-fullscreen-button,
+audio::-webkit-media-controls-fullscreen-button,
+video::-webkit-media-controls-wireless-playback-picker-button,
+audio::-webkit-media-controls-wireless-playback-picker-button,
+video::-webkit-media-controls-picture-in-picture-button,
+audio::-webkit-media-controls-picture-in-picture-button {
+ -webkit-appearance: none;
+ display: block;
+ padding: 0;
+ border: 0;
+ -webkit-user-select: none;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ mix-blend-mode: plus-darker;
+ opacity: 0.55;
+ -webkit-mask-position: 50% 50%;
+ -webkit-mask-repeat: no-repeat;
+ -webkit-mask-origin: border-box;
+ background-color: black;
+ transition: background-color 250ms;
+}
+
+video::-webkit-media-controls-play-button,
+video::-webkit-media-controls-fullscreen-button,
+video::-webkit-media-controls-wireless-playback-picker-button,
+video::-webkit-media-controls-picture-in-picture-button {
+ -webkit-transform: translate3d(0, 0, 0);
+}
+
+video::-webkit-media-controls-play-button:active,
+audio::-webkit-media-controls-play-button:active,
+video::-webkit-media-controls-fullscreen-button:active,
+audio::-webkit-media-controls-fullscreen-button:active,
+audio::-webkit-media-controls-wireless-playback-picker-button:active,
+video::-webkit-media-controls-wireless-playback-picker-button:active,
+video::-webkit-media-controls-picture-in-picture-button:active,
+audio::-webkit-media-controls-picture-in-picture-button:active {
+ mix-blend-mode: normal;
+ opacity: 1;
+ background-color: white;
+ transition: background-color 0ms;
+}
+
+/* ================== PLAY BUTTON ====================== */
+
+audio::-webkit-media-controls-play-button,
+video::-webkit-media-controls-play-button {
+ margin-left: 4px;
+ -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAsCAYAAAANUxr1AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFVJREFUeNrs2bENgEAMA0AHsf/EIB5GcIGgOEvpIuf6TJIjXfZyv+qfe67ywJT7Vf+WnwUICAgICAgICAgICAgICAgICAgICAjoyzxvgvPlG1X/EmAASwgGTpkrSW0AAAAASUVORK5CYII=');
+ -webkit-mask-size: 18px 22px;
+ -webkit-order: 1;
+}
+
+audio::-webkit-media-controls-play-button {
+ width: 42px;
+ height: 39px;
+}
+
+video::-webkit-media-controls-play-button {
+ width: 44px;
+ height: 50px;
+}
+
+audio::-webkit-media-controls-play-button.paused,
+video::-webkit-media-controls-play-button.paused {
+ -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAsCAYAAAATmipGAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUZJREFUeNrU2S8sRWEYgPFzD7PZbDYbQSAoBBKFwgQTmKRpJE3SaBKFRKJJJphiCslNt1AERRBsNpvN/DmeL7ybGfLzne3Xn517d873vqcsimIOtSKDq0Id0zmEhktM5hAazjGWQ2g4xUgOocknjjFkDw0fOMSAPfR78AH67KHhDXvotYeGV+yg2x4aXrCFLntoeMYGOuyh4QnraLeHhkesos0eGh6wglZ7aLjHMlrsoeEOi2i2h4ZbLKC0h4ZrzP8XXMk0MPvbeFRJXf0cjyq5NB5NlIX/qsX/1non6/afvvHXrsESeGN/PMUDv8n8Cl0yv0L1hxL9MU9/cE6jyJp5FNEPd2lc3kSneQGxbV5ApJXOLnqsK5137JuXZLF27Dcvco8waF7knmDYvBo/w6j5Y8MFxs2fb9LEN2UfnmaKDD4xfgkwANpCXZrruKGvAAAAAElFTkSuQmCC');
+ -webkit-mask-size: 21px 22px;
+}
+
+/* ================== FULLSCREEN BUTTON ====================== */
+
+video::-webkit-media-controls-fullscreen-button {
+ width: 44px;
+ height: 50px;
+ -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO5JREFUeNrs2F8KwjAMBvAoXsID6GEUPIKPw2sLPiu+CCIYO1nFhf1pWkw+YR9kTyv8KCNpR0TEyjqSUZiZ5vQH+d6dZ6gDDIy5BbyjISVwi4aUQEJDdgGhkH1AGOQQEAI5BnRHpgBdkalAN2QEnpsiNGQEamKKzAFK5CPUCg0YkbdQe8QdjFkifoPmjRr+wDoBJ2Bh1r9uRaW4UzNxKsQ2sxFjsUID1tmVIK0adQ7y/QfDcpJokeZALdIFqEG6AVORrsAUpDtwDAkBHELCAPuQUMAuJBxQIj/AheUVI2eRJfCifP9aP2boV7uXAAMArhj6EAChbh4AAAAASUVORK5CYII=');
+ -webkit-mask-size: 20px 20px;
+ -webkit-order: 5;
+}
+
+/* ================== AIRPLAY BUTTON ====================== */
+
+audio::-webkit-media-controls-wireless-playback-picker-button,
+video::-webkit-media-controls-wireless-playback-picker-button {
+ -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAkCAYAAAD/yagrAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAARlJREFUeNrsmDEOgjAUhtuYMLgwuTG6Mrt7AC/gCVycHLwBN+AEXIADuDO76ubm5OJgYnw+kldDiEb6aElJ3ku+gYSWL4/yl1QrpUCNoLSIehbVgfmBiIqoiIqoiIqoiIpoUKLmAhz+7u2QpUPRj5tL0TXyQm5IGqpo3cVHY64LkoQmmlIXocURiUMRnSHnL5KGAxK5Fu1CiUxooilSdRhTNBIlpk7bPtfq5orkFMmWFmOzRqcSWsNeRE/0mk3ljK5sOqzrn6Kc2jMka57IaqjdwmQlMLkjC9+S7azkckXmviRt15TtmndSnK/UNkV6Fzf3OLnMroh2FvBM3vcArRhA0rDlimYDSrIzdjOw5N+M1QCjOMdVbwEGAJbOn8QOMeI8AAAAAElFTkSuQmCC');
+ -webkit-mask-size: 21px 18px;
+ -webkit-order: 3;
+}
+
+audio::-webkit-media-controls-wireless-playback-picker-button {
+ width: 44px;
+ height: 39px;
+}
+
+video::-webkit-media-controls-wireless-playback-picker-button {
+ width: 44px;
+ height: 50px;
+}
+
+audio::-webkit-media-controls-wireless-playback-picker-button.playing,
+video::-webkit-media-controls-wireless-playback-picker-button.playing {
+ opacity: 1;
+ mix-blend-mode: normal;
+ background-color: -apple-wireless-playback-target-active;
+}
+
+/* ================== PICTURE IN PICTURE BUTTON ====================== */
+
+video::-webkit-media-controls-picture-in-picture-button {
+ width: 35px;
+ height: 44px;
+ -webkit-order: 5;
+ -webkit-mask-size: 23px 18px;
+ -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAkCAYAAAD2IghRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAANhJREFUeNrslzEOwjAMRR2UA3AQ7sLKwsTEglg6sTIgLgs7qCZBRbIsWpDayHH1v/SHJmn0ZFutHYiIqX4F+cDMtCCncgseh1JiLJ5lxH+Br2qGZ2Gpa/IjeVMZ1/ur0ndgLdaeydsvF9/7LrYEz7oo+J0X8Kyz2GuT917As04K/uAFPKtR5xov4FnHLuIy+i7AqavxVr1jAh7H/HbRZE3QZGndBvaW1k1Z6VqdHtjzIBELZrHoKIfRzbpUxqY4IOJzLpWAiAMc4AAHOMAB/m9T9Bn1veklwACtBYmnlYBaIQAAAABJRU5ErkJggg==');
+}
+
+video::-webkit-media-controls-picture-in-picture-button.return-from-picture-in-picture {
+ -webkit-mask-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAkCAYAAAD2IghRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAOZJREFUeNpiZGBg+M8w+AEjMuf///8MTAxDFAxZh7Pgi5IBBv+HZYgPS4f/JwJ/GA1xCjMnIfAejf9poHPvfyy5GFvS6KdjyYPLXeAKiFiH/0ZiT6GT46ni8CAg/onEn0WH/EEVh4OAL5rjFwIx81BwOAh4APE3JPGlNHQ8VR0OAs5A/BVJbjUQsw0Fh4OAHRB/RpLPG0wOJwSsoTXnfBolF5o5HARUBqL2pYbDB6bGBDqchYYlAk27cqPt8YFuHVIaxYyjIT6ckwrjaIiPOnzU4aMOH3X4qMNHHU5sowjWmxhqACDAAI3lmdvpn4aTAAAAAElFTkSuQmCC');
+}
+
+/* ================== SPACER ====================== */
+
+video::-webkit-media-controls-spacer,
+audio::-webkit-media-controls-spacer {
+ -webkit-appearance: none !important;
+ -webkit-flex: 1 1 0; /* Should be the same as the timeline container. */
+ -webkit-order: 2;
+ height: 8px;
+ margin: 0;
+ background-color: transparent !important;
+}
+
+/* ================== TIMELINE ====================== */
+
+video::-webkit-media-controls-timeline,
+audio::-webkit-media-controls-timeline {
+ -webkit-appearance: none !important;
+ -webkit-flex: 1 1 0;
+ height: 8px;
+ margin: 0;
+ background-color: transparent !important;
+ background-size: 100% 100%;
+ border: none !important;
+ border-radius: 0 !important;
+ box-sizing: content-box !important;
+}
+
+video::-webkit-media-controls-timeline {
+ -webkit-transform: translate3d(0, 0, 0);
+ mix-blend-mode: plus-darker;
+}
+
+audio::-webkit-media-controls-timeline::-webkit-slider-runnable-track,
+video::-webkit-media-controls-timeline::-webkit-slider-runnable-track {
+ -webkit-appearance: none !important;
+ background: none !important;
+ border: none !important;
+}
+
+video::-webkit-media-controls-timeline::-webkit-slider-thumb,
+audio::-webkit-media-controls-timeline::-webkit-slider-thumb {
+ -webkit-appearance: none !important;
+ width: 15px;
+ height: 50px;
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAiCAYAAABIiGl0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAxNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iUGl4ZWxtYXRvciAyLjIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTkwQ0UwODdBQTcxMTFFNEE5QTZGQTVGMjFBNkUxN0UiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTkwQ0UwODhBQTcxMTFFNEE5QTZGQTVGMjFBNkUxN0UiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1OTBDRTA4NUFBNzExMUU0QTlBNkZBNUYyMUE2RTE3RSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1OTBDRTA4NkFBNzExMUU0QTlBNkZBNUYyMUE2RTE3RSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pofoz4wAAAJGSURBVHjaxJfNTsJAEMfbLmoNaKIhIE18A+LReOXmi/gQnnwSLz6Fnjh4Id4kHElI5EDwpIkgImWdf/Nfs5ISCrR1kkmg7c5vZz/mw3WSy66oL7ojqqiQkPotOhGdaq1XGnMTvC+KlprN5kW9Xr8sFotnhUKhCsUHs9lsCB2NRu1Op3PfaDRa8vhDdCSqnQ0E3lX7/f6NGH7RCQXfYgzG0sZacthut6/G4/Gj3lAwFjZgKyn0qNfrXYdh+Ka3FNiALdhc6Sk+1CkL4Us997E0aXga5zmX3Y87vdVt9jTJnvPA/blJJZxEnbHwtJds8Mk6V2ZTAQMsAD1EJAQHpdSpk7GAARaYAPuISE5OQpYfeYwwmBeYrMhjZeJuHkKWAtj7B7DnJchQaQsylgvwHGktL6qwXsH0TE7NETw091gjiecFFtYzmNFSo3LICyysBzBxsI5xoWUJWllHL8lSfTnViFyTyGO4PhgMbrP2lgxNpnPAdBXkkBYDsg48lqbRDLrd7t18Pn9P21PYhG3zl8yoXsY+1zCjDEufgIxjMqPCHMm5wpdBBsVeQK2QpUzZs8dKsGY+SrG8DSxvj8j6DdOKHUPZhkO3KOiDBWiZDGW3MC4f7FPVYvKQyuE8YQvzFJMUcJg+qfit3YVKc4clqB8H3zAThWzmJmzstGN1fI511LVJXYzl7hZQ00F+2dA4sH3P7Am4a0zAeDklEODZYueoVgz+DS4rWk5tTdj2cmqt4tr9sccJFqyG3N4CGxrSu3AZ0MiPAAMAZrLkuVVmRJsAAAAASUVORK5CYII=');
+ background-repeat: no-repeat;
+ background-size: 15px 17px;
+ background-position: 0px 18px;
+ background-color: transparent !important;
+ border: none !important;
+ -webkit-transform: rotateZ(0deg);
+}
+
+video::-webkit-media-controls-timeline::-webkit-slider-thumb:active,
+audio::-webkit-media-controls-timeline::-webkit-slider-thumb:active {
+ background-color: transparent !important;
+}
+
+audio::-webkit-media-controls-timeline::-webkit-slider-thumb {
+ height: 39px;
+ background-position: 0px 12px;
+}
+
+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;
+ -webkit-align-items: center;
+ overflow-y: hidden;
+ overflow-x: hidden;
+ letter-spacing: normal;
+ word-spacing: normal;
+ line-height: normal;
+ text-transform: none;
+ text-indent: 0px;
+ text-decoration: none;
+ color: black;
+ mix-blend-mode: plus-darker;
+ opacity: 0.55;
+ height: 50px;
+ font-size: 13px;
+ font-family: -apple-system-monospaced-numbers;
+ -webkit-text-zoom: reset;
+}
+
+audio::-webkit-media-controls-current-time-display,
+video::-webkit-media-controls-current-time-display {
+ min-width: 32px;
+ -webkit-justify-content: flex-end;
+ padding-right: 6px;
+}
+
+audio::-webkit-media-controls-time-remaining-display,
+video::-webkit-media-controls-time-remaining-display {
+ min-width: 38px;
+ -webkit-justify-content: flex-start;
+ padding-left: 6px;
+ margin-right: 6px;
+}
+
+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; /* Any changes here should also be made on the spacer. */
+ position: relative;
+ padding: 0;
+ -webkit-order: 2;
+ -webkit-text-size-adjust: auto;
+}
+
+audio::-webkit-media-controls-timeline-container {
+ padding-right: 10px;
+}
+
+audio::-webkit-media-controls-current-time-display.three-digit-time,
+video::-webkit-media-controls-current-time-display.three-digit-time {
+ min-width: 31px;
+}
+
+audio::-webkit-media-controls-time-remaining-display.three-digit-time,
+video::-webkit-media-controls-time-remaining-display.three-digit-time {
+ min-width: 40px;
+}
+
+audio::-webkit-media-controls-current-time-display.four-digit-time,
+video::-webkit-media-controls-current-time-display.four-digit-time {
+ min-width: 40px;
+}
+
+audio::-webkit-media-controls-time-remaining-display.four-digit-time,
+video::-webkit-media-controls-time-remaining-display.four-digit-time {
+ min-width: 49px;
+}
+
+audio::-webkit-media-controls-current-time-display.five-digit-time,
+video::-webkit-media-controls-current-time-display.five-digit-time {
+ min-width: 51px;
+}
+
+audio::-webkit-media-controls-time-remaining-display.five-digit-time,
+video::-webkit-media-controls-time-remaining-display.five-digit-time {
+ min-width: 60px;
+}
+
+audio::-webkit-media-controls-current-time-display.six-digit-time,
+video::-webkit-media-controls-current-time-display.six-digit-time {
+ min-width: 60px;
+}
+
+audio::-webkit-media-controls-time-remaining-display.six-digit-time,
+video::-webkit-media-controls-time-remaining-display.six-digit-time {
+ min-width: 69px;
+}
+
+/* ================== STATUS DISPLAY ====================== */
+
+video::-webkit-media-controls-status-display,
+audio::-webkit-media-controls-status-display {
+
+ overflow: hidden;
+ font-family: -apple-system;
+ letter-spacing: normal;
+ word-spacing: normal;
+ line-height: normal;
+ text-transform: none;
+ text-indent: 0px;
+ text-decoration: none;
+ color: black;
+ mix-blend-mode: plus-darker;
+ opacity: 0.55;
+ -webkit-order: 2;
+}
+
+/* ================== CAPTIONS ====================== */
+
+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;
+ word-break: break-word;
+
+ -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;
+}
+
+/* ==================== PLAYBACK 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 131 90"><g fill="none" stroke="-apple-system-gray" stroke-width="4"><rect x="2" y="2" width="127" height="76"/><line x1="30" y1="88" x2="101" y2="88"/></g></svg>');
+ 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: -apple-system;
+ font-weight: 400;
+ 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: 12pt;
+ font-size: 12pt;
+ text-align: center;
+ margin: 0px;
+ color: -apple-system-gray;
+}
+
+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: 10pt;
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 62 43"><g fill="none" stroke="rgb(146,146,146)" stroke-width="2"><rect x="1" y="1" width="60" height="36"/><line x1="14" y1="42" x2="48" y2="42"/></g></svg>');
+ 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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 304 150"><g fill="none" stroke="-apple-system-gray" stroke-width="3"><path d="m 172,106 -81,0 c -3.5,0 -6,-2.5 -6,-6 l 0,-89 c 0,-3.5 2.5,-6 6,-6 l 122,0 c 3.5,0 6,2.5 6,6 l 0,57" /><path d="m 233,119 -53,0 c -3,0 -3,-0 -3,-3 l 0,-40 c 0,-3 0,-3 3,-3 l 53,0 c 3,0 3,0 3,3 l 0,40 c 0,3 0,3 -3,3 z" /></g></svg>');
+}
+
+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;
+}
+
+/* When PiP is active, we don't want the controls container showing. */
+video::-webkit-media-controls-panel-container.picture-in-picture {
+ opacity: 0;
+ pointer-events: none;
+}
+
+/* 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/mediaControlsiOS.js b/Source/WebCore/Modules/mediacontrols/mediaControlsiOS.js
new file mode 100644
index 000000000..9d6d28abb
--- /dev/null
+++ b/Source/WebCore/Modules/mediacontrols/mediaControlsiOS.js
@@ -0,0 +1,628 @@
+function createControls(root, video, host)
+{
+ return new ControllerIOS(root, video, host);
+};
+
+function ControllerIOS(root, video, host)
+{
+ this.doingSetup = true;
+ this._pageScaleFactor = 1;
+
+ this.timelineContextName = "_webkit-media-controls-timeline-" + host.generateUUID();
+
+ Controller.call(this, root, video, host);
+
+ this.setNeedsTimelineMetricsUpdate();
+
+ this._timelineIsHidden = false;
+ this._currentDisplayWidth = 0;
+ this.scheduleUpdateLayoutForDisplayedWidth();
+
+ host.controlsDependOnPageScaleFactor = true;
+ this.doingSetup = false;
+};
+
+/* Enums */
+ControllerIOS.StartPlaybackControls = 2;
+
+ControllerIOS.prototype = {
+ /* Constants */
+ MinimumTimelineWidth: 150,
+ ButtonWidth: 42,
+
+ get idiom()
+ {
+ return "ios";
+ },
+
+ createBase: function() {
+ Controller.prototype.createBase.call(this);
+
+ var startPlaybackButton = this.controls.startPlaybackButton = document.createElement('div');
+ startPlaybackButton.setAttribute('pseudo', '-webkit-media-controls-start-playback-button');
+ startPlaybackButton.setAttribute('aria-label', this.UIString('Start Playback'));
+ startPlaybackButton.setAttribute('role', 'button');
+
+ var startPlaybackBackground = document.createElement('div');
+ startPlaybackBackground.setAttribute('pseudo', '-webkit-media-controls-start-playback-background');
+ startPlaybackBackground.classList.add('webkit-media-controls-start-playback-background');
+ startPlaybackButton.appendChild(startPlaybackBackground);
+
+ var startPlaybackGlyph = document.createElement('div');
+ startPlaybackGlyph.setAttribute('pseudo', '-webkit-media-controls-start-playback-glyph');
+ startPlaybackGlyph.classList.add('webkit-media-controls-start-playback-glyph');
+ startPlaybackButton.appendChild(startPlaybackGlyph);
+
+ this.listenFor(this.base, 'gesturestart', this.handleBaseGestureStart);
+ this.listenFor(this.base, 'gesturechange', this.handleBaseGestureChange);
+ this.listenFor(this.base, 'gestureend', this.handleBaseGestureEnd);
+ this.listenFor(this.base, 'touchstart', this.handleWrapperTouchStart);
+ this.stopListeningFor(this.base, 'mousemove', this.handleWrapperMouseMove);
+ this.stopListeningFor(this.base, 'mouseout', this.handleWrapperMouseOut);
+
+ this.listenFor(document, 'visibilitychange', this.handleVisibilityChange);
+ },
+
+ shouldHaveStartPlaybackButton: function() {
+ var allowsInline = this.host.allowsInlineMediaPlayback;
+
+ if (this.isPlaying || (this.hasPlayed && allowsInline))
+ return false;
+
+ if (this.isAudio() && allowsInline)
+ return false;
+
+ if (this.doingSetup)
+ return true;
+
+ if (this.isFullScreen())
+ return false;
+
+ if (!this.video.currentSrc && this.video.error)
+ return false;
+
+ if (!this.video.controls && allowsInline)
+ return false;
+
+ if (this.video.currentSrc && this.video.error)
+ return true;
+
+ return true;
+ },
+
+ shouldHaveControls: function() {
+ if (this.shouldHaveStartPlaybackButton())
+ return false;
+
+ return Controller.prototype.shouldHaveControls.call(this);
+ },
+
+ shouldHaveAnyUI: function() {
+ return this.shouldHaveStartPlaybackButton() || Controller.prototype.shouldHaveAnyUI.call(this) || this.currentPlaybackTargetIsWireless();
+ },
+
+ createControls: function() {
+ Controller.prototype.createControls.call(this);
+
+ var panelContainer = this.controls.panelContainer = document.createElement('div');
+ panelContainer.setAttribute('pseudo', '-webkit-media-controls-panel-container');
+
+ var wirelessTargetPicker = this.controls.wirelessTargetPicker;
+ this.listenFor(wirelessTargetPicker, 'touchstart', this.handleWirelessPickerButtonTouchStart);
+ this.listenFor(wirelessTargetPicker, 'touchend', this.handleWirelessPickerButtonTouchEnd);
+ this.listenFor(wirelessTargetPicker, 'touchcancel', this.handleWirelessPickerButtonTouchCancel);
+
+ this.listenFor(this.controls.startPlaybackButton, 'touchstart', this.handleStartPlaybackButtonTouchStart);
+ this.listenFor(this.controls.startPlaybackButton, 'touchend', this.handleStartPlaybackButtonTouchEnd);
+ this.listenFor(this.controls.startPlaybackButton, 'touchcancel', this.handleStartPlaybackButtonTouchCancel);
+
+ this.listenFor(this.controls.panel, 'touchstart', this.handlePanelTouchStart);
+ this.listenFor(this.controls.panel, 'touchend', this.handlePanelTouchEnd);
+ this.listenFor(this.controls.panel, 'touchcancel', this.handlePanelTouchCancel);
+ this.listenFor(this.controls.playButton, 'touchstart', this.handlePlayButtonTouchStart);
+ this.listenFor(this.controls.playButton, 'touchend', this.handlePlayButtonTouchEnd);
+ this.listenFor(this.controls.playButton, 'touchcancel', this.handlePlayButtonTouchCancel);
+ this.listenFor(this.controls.fullscreenButton, 'touchstart', this.handleFullscreenTouchStart);
+ this.listenFor(this.controls.fullscreenButton, 'touchend', this.handleFullscreenTouchEnd);
+ this.listenFor(this.controls.fullscreenButton, 'touchcancel', this.handleFullscreenTouchCancel);
+ this.listenFor(this.controls.pictureInPictureButton, 'touchstart', this.handlePictureInPictureTouchStart);
+ this.listenFor(this.controls.pictureInPictureButton, 'touchend', this.handlePictureInPictureTouchEnd);
+ this.listenFor(this.controls.pictureInPictureButton, 'touchcancel', this.handlePictureInPictureTouchCancel);
+ this.listenFor(this.controls.timeline, 'touchstart', this.handleTimelineTouchStart);
+ this.stopListeningFor(this.controls.playButton, 'click', this.handlePlayButtonClicked);
+
+ this.controls.timeline.style.backgroundImage = '-webkit-canvas(' + this.timelineContextName + ')';
+ },
+
+ setControlsType: function(type) {
+ if (type === this.controlsType)
+ return;
+ Controller.prototype.setControlsType.call(this, type);
+
+ if (type === ControllerIOS.StartPlaybackControls)
+ this.addStartPlaybackControls();
+ else
+ this.removeStartPlaybackControls();
+ },
+
+ addStartPlaybackControls: function() {
+ this.base.appendChild(this.controls.startPlaybackButton);
+ this.showShowControlsButton(false);
+ },
+
+ removeStartPlaybackControls: function() {
+ if (this.controls.startPlaybackButton.parentNode)
+ this.controls.startPlaybackButton.parentNode.removeChild(this.controls.startPlaybackButton);
+ },
+
+ reconnectControls: function()
+ {
+ Controller.prototype.reconnectControls.call(this);
+
+ if (this.controlsType === ControllerIOS.StartPlaybackControls)
+ this.addStartPlaybackControls();
+ },
+
+ configureInlineControls: function() {
+ this.controls.inlinePlaybackPlaceholder.appendChild(this.controls.inlinePlaybackPlaceholderText);
+ this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextTop);
+ this.controls.inlinePlaybackPlaceholderText.appendChild(this.controls.inlinePlaybackPlaceholderTextBottom);
+ this.controls.panel.appendChild(this.controls.playButton);
+ this.controls.panel.appendChild(this.controls.statusDisplay);
+ this.controls.panel.appendChild(this.controls.timelineBox);
+ this.controls.panel.appendChild(this.controls.wirelessTargetPicker);
+ if (!this.isLive) {
+ this.controls.timelineBox.appendChild(this.controls.currentTime);
+ this.controls.timelineBox.appendChild(this.controls.timeline);
+ this.controls.timelineBox.appendChild(this.controls.remainingTime);
+ }
+ if (this.isAudio()) {
+ // Hide the scrubber on audio until the user starts playing.
+ this.controls.timelineBox.classList.add(this.ClassNames.hidden);
+ } else {
+ this.updatePictureInPictureButton();
+ this.controls.panel.appendChild(this.controls.fullscreenButton);
+ }
+ },
+
+ configureFullScreenControls: function() {
+ // Explicitly do nothing to override base-class behavior.
+ },
+
+ controlsAreHidden: function()
+ {
+ // Controls are only ever actually hidden when they are removed from the tree
+ return !this.controls.panelContainer.parentElement;
+ },
+
+ addControls: function() {
+ this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
+ this.base.appendChild(this.controls.panelContainer);
+ this.controls.panelContainer.appendChild(this.controls.panelBackground);
+ this.controls.panelContainer.appendChild(this.controls.panel);
+ this.setNeedsTimelineMetricsUpdate();
+ },
+
+ updateControls: function() {
+ if (this.shouldHaveStartPlaybackButton())
+ this.setControlsType(ControllerIOS.StartPlaybackControls);
+ else if (this.presentationMode() === "fullscreen")
+ this.setControlsType(Controller.FullScreenControls);
+ else
+ this.setControlsType(Controller.InlineControls);
+
+ this.updateLayoutForDisplayedWidth();
+ this.setNeedsTimelineMetricsUpdate();
+ },
+
+ drawTimelineBackground: function() {
+ var width = this.timelineWidth * window.devicePixelRatio;
+ var height = this.timelineHeight * window.devicePixelRatio;
+
+ if (!width || !height)
+ return;
+
+ var played = this.video.currentTime / this.video.duration;
+ var buffered = 0;
+ var bufferedRanges = this.video.buffered;
+ if (bufferedRanges && bufferedRanges.length)
+ buffered = Math.max(bufferedRanges.end(bufferedRanges.length - 1), buffered);
+
+ buffered /= this.video.duration;
+ buffered = Math.max(buffered, played);
+
+ var ctx = document.getCSSCanvasContext('2d', this.timelineContextName, width, height);
+
+ ctx.clearRect(0, 0, width, height);
+
+ var midY = height / 2;
+
+ // 1. Draw the buffered part and played parts, using
+ // solid rectangles that are clipped to the outside of
+ // the lozenge.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, 1, midY - 3, width - 2, 6, 3);
+ ctx.closePath();
+ ctx.clip();
+ ctx.fillStyle = "white";
+ ctx.fillRect(0, 0, Math.round(width * played) + 2, height);
+ ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
+ ctx.fillRect(Math.round(width * played) + 2, 0, Math.round(width * (buffered - played)) + 2, height);
+ ctx.restore();
+
+ // 2. Draw the outline with a clip path that subtracts the
+ // middle of a lozenge. This produces a better result than
+ // stroking.
+ ctx.save();
+ ctx.beginPath();
+ this.addRoundedRect(ctx, 1, midY - 3, width - 2, 6, 3);
+ this.addRoundedRect(ctx, 2, midY - 2, width - 4, 4, 2);
+ ctx.closePath();
+ ctx.clip("evenodd");
+ ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
+ ctx.fillRect(Math.round(width * buffered) + 2, 0, width, height);
+ ctx.restore();
+ },
+
+ formatTime: function(time) {
+ if (isNaN(time))
+ time = 0;
+ var absTime = Math.abs(time);
+ var intSeconds = Math.floor(absTime % 60).toFixed(0);
+ var intMinutes = Math.floor((absTime / 60) % 60).toFixed(0);
+ var intHours = Math.floor(absTime / (60 * 60)).toFixed(0);
+ var sign = time < 0 ? '-' : String();
+
+ if (intHours > 0)
+ return sign + intHours + ':' + String('0' + intMinutes).slice(-2) + ":" + String('0' + intSeconds).slice(-2);
+
+ return sign + String('0' + intMinutes).slice(intMinutes >= 10 ? -2 : -1) + ":" + String('0' + intSeconds).slice(-2);
+ },
+
+ handlePlayButtonTouchStart: function() {
+ this.controls.playButton.classList.add('active');
+ },
+
+ handlePlayButtonTouchEnd: function(event) {
+ this.controls.playButton.classList.remove('active');
+
+ if (this.canPlay()) {
+ this.video.play();
+ this.showControls();
+ } else
+ this.video.pause();
+
+ return true;
+ },
+
+ handlePlayButtonTouchCancel: function(event) {
+ this.controls.playButton.classList.remove('active');
+ return true;
+ },
+
+ handleBaseGestureStart: function(event) {
+ this.gestureStartTime = new Date();
+ // If this gesture started with two fingers inside the video, then
+ // don't treat it as a potential zoom, unless we're still waiting
+ // to play.
+ if (this.mostRecentNumberOfTargettedTouches == 2 && this.controlsType != ControllerIOS.StartPlaybackControls)
+ event.preventDefault();
+ },
+
+ handleBaseGestureChange: function(event) {
+ if (!this.video.controls || this.isAudio() || this.isFullScreen() || this.gestureStartTime === undefined || this.controlsType == ControllerIOS.StartPlaybackControls)
+ return;
+
+ var scaleDetectionThreshold = 0.2;
+ if (event.scale > 1 + scaleDetectionThreshold || event.scale < 1 - scaleDetectionThreshold)
+ delete this.lastDoubleTouchTime;
+
+ if (this.mostRecentNumberOfTargettedTouches == 2 && event.scale >= 1.0)
+ event.preventDefault();
+
+ var currentGestureTime = new Date();
+ var duration = (currentGestureTime - this.gestureStartTime) / 1000;
+ if (!duration)
+ return;
+
+ var velocity = Math.abs(event.scale - 1) / duration;
+
+ var pinchOutVelocityThreshold = 2;
+ var pinchOutGestureScaleThreshold = 1.25;
+ if (velocity < pinchOutVelocityThreshold || event.scale < pinchOutGestureScaleThreshold)
+ return;
+
+ delete this.gestureStartTime;
+ this.video.webkitEnterFullscreen();
+ },
+
+ handleBaseGestureEnd: function(event) {
+ delete this.gestureStartTime;
+ },
+
+ handleWrapperTouchStart: function(event) {
+ if (event.target != this.base && event.target != this.controls.inlinePlaybackPlaceholder)
+ return;
+
+ this.mostRecentNumberOfTargettedTouches = event.targetTouches.length;
+
+ if (this.controlsAreHidden() || !this.controls.panel.classList.contains(this.ClassNames.show)) {
+ this.showControls();
+ this.resetHideControlsTimer();
+ } else if (!this.canPlay())
+ this.hideControls();
+ },
+
+ handlePanelTouchStart: function(event) {
+ this.video.style.webkitUserSelect = 'none';
+ },
+
+ handlePanelTouchEnd: function(event) {
+ this.video.style.removeProperty('-webkit-user-select');
+ },
+
+ handlePanelTouchCancel: function(event) {
+ this.video.style.removeProperty('-webkit-user-select');
+ },
+
+ handleVisibilityChange: function(event) {
+ this.updateShouldListenForPlaybackTargetAvailabilityEvent();
+ },
+
+ handlePanelTransitionEnd: function(event)
+ {
+ var opacity = window.getComputedStyle(this.controls.panel).opacity;
+ if (!parseInt(opacity) && !this.controlsAlwaysVisible()) {
+ this.base.removeChild(this.controls.inlinePlaybackPlaceholder);
+ this.base.removeChild(this.controls.panelContainer);
+ }
+ },
+
+ handleFullscreenButtonClicked: function(event) {
+ if ('webkitSetPresentationMode' in this.video) {
+ if (this.presentationMode() === 'fullscreen')
+ this.video.webkitSetPresentationMode('inline');
+ else
+ this.video.webkitSetPresentationMode('fullscreen');
+
+ return;
+ }
+
+ if (this.isFullScreen())
+ this.video.webkitExitFullscreen();
+ else
+ this.video.webkitEnterFullscreen();
+ },
+
+ handleFullscreenTouchStart: function() {
+ this.controls.fullscreenButton.classList.add('active');
+ },
+
+ handleFullscreenTouchEnd: function(event) {
+ this.controls.fullscreenButton.classList.remove('active');
+
+ this.handleFullscreenButtonClicked();
+
+ return true;
+ },
+
+ handleFullscreenTouchCancel: function(event) {
+ this.controls.fullscreenButton.classList.remove('active');
+ return true;
+ },
+
+ handlePictureInPictureTouchStart: function() {
+ this.controls.pictureInPictureButton.classList.add('active');
+ },
+
+ handlePictureInPictureTouchEnd: function(event) {
+ this.controls.pictureInPictureButton.classList.remove('active');
+
+ this.handlePictureInPictureButtonClicked();
+
+ return true;
+ },
+
+ handlePictureInPictureTouchCancel: function(event) {
+ this.controls.pictureInPictureButton.classList.remove('active');
+ return true;
+ },
+
+ handleStartPlaybackButtonTouchStart: function(event) {
+ this.controls.startPlaybackButton.classList.add('active');
+ this.controls.startPlaybackButton.querySelector('.webkit-media-controls-start-playback-glyph').classList.add('active');
+ },
+
+ handleStartPlaybackButtonTouchEnd: function(event) {
+ this.controls.startPlaybackButton.classList.remove('active');
+ this.controls.startPlaybackButton.querySelector('.webkit-media-controls-start-playback-glyph').classList.remove('active');
+
+ if (this.video.error)
+ return true;
+
+ this.video.play();
+ this.canToggleShowControlsButton = true;
+ this.updateControls();
+
+ return true;
+ },
+
+ handleStartPlaybackButtonTouchCancel: function(event) {
+ this.controls.startPlaybackButton.classList.remove('active');
+ return true;
+ },
+
+ handleTimelineTouchStart: function(event) {
+ this.scrubbing = true;
+ this.listenFor(this.controls.timeline, 'touchend', this.handleTimelineTouchEnd);
+ this.listenFor(this.controls.timeline, 'touchcancel', this.handleTimelineTouchEnd);
+ },
+
+ handleTimelineTouchEnd: function(event) {
+ this.stopListeningFor(this.controls.timeline, 'touchend', this.handleTimelineTouchEnd);
+ this.stopListeningFor(this.controls.timeline, 'touchcancel', this.handleTimelineTouchEnd);
+ this.scrubbing = false;
+ },
+
+ handleWirelessPickerButtonTouchStart: function() {
+ if (!this.video.error)
+ this.controls.wirelessTargetPicker.classList.add('active');
+ },
+
+ handleWirelessPickerButtonTouchEnd: function(event) {
+ this.controls.wirelessTargetPicker.classList.remove('active');
+ return this.handleWirelessPickerButtonClicked();
+ },
+
+ handleWirelessPickerButtonTouchCancel: function(event) {
+ this.controls.wirelessTargetPicker.classList.remove('active');
+ return true;
+ },
+
+ updateShouldListenForPlaybackTargetAvailabilityEvent: function() {
+ if (this.controlsType === ControllerIOS.StartPlaybackControls) {
+ this.setShouldListenForPlaybackTargetAvailabilityEvent(false);
+ return;
+ }
+
+ Controller.prototype.updateShouldListenForPlaybackTargetAvailabilityEvent.call(this);
+ },
+
+ updateWirelessTargetPickerButton: function() {
+ },
+
+ updateStatusDisplay: function(event)
+ {
+ this.controls.startPlaybackButton.classList.toggle(this.ClassNames.failed, this.video.error !== null);
+ this.controls.startPlaybackButton.querySelector(".webkit-media-controls-start-playback-glyph").classList.toggle(this.ClassNames.failed, this.video.error !== null);
+ Controller.prototype.updateStatusDisplay.call(this, event);
+ },
+
+ setPlaying: function(isPlaying)
+ {
+ Controller.prototype.setPlaying.call(this, isPlaying);
+
+ this.updateControls();
+
+ if (isPlaying && this.isAudio())
+ this.controls.timelineBox.classList.remove(this.ClassNames.hidden);
+
+ if (isPlaying)
+ this.hasPlayed = true;
+ else
+ this.showControls();
+ },
+
+ showControls: function()
+ {
+ this.updateShouldListenForPlaybackTargetAvailabilityEvent();
+ if (!this.video.controls)
+ return;
+
+ this.updateForShowingControls();
+ if (this.shouldHaveControls() && !this.controls.panelContainer.parentElement) {
+ this.base.appendChild(this.controls.inlinePlaybackPlaceholder);
+ this.base.appendChild(this.controls.panelContainer);
+ this.showShowControlsButton(false);
+ }
+ },
+
+ setShouldListenForPlaybackTargetAvailabilityEvent: function(shouldListen)
+ {
+ if (shouldListen && (this.shouldHaveStartPlaybackButton() || this.video.error))
+ return;
+
+ Controller.prototype.setShouldListenForPlaybackTargetAvailabilityEvent.call(this, shouldListen);
+ },
+
+ shouldReturnVideoLayerToInline: function()
+ {
+ return this.presentationMode() === 'inline';
+ },
+
+ updatePictureInPicturePlaceholder: function(event)
+ {
+ var presentationMode = this.presentationMode();
+
+ switch (presentationMode) {
+ case 'inline':
+ this.controls.panelContainer.classList.remove(this.ClassNames.pictureInPicture);
+ break;
+ case 'picture-in-picture':
+ this.controls.panelContainer.classList.add(this.ClassNames.pictureInPicture);
+ break;
+ default:
+ this.controls.panelContainer.classList.remove(this.ClassNames.pictureInPicture);
+ break;
+ }
+
+ Controller.prototype.updatePictureInPicturePlaceholder.call(this, event);
+ },
+
+ // Due to the bad way we are faking inheritance here, in particular the extends method
+ // on Controller.prototype, we don't copy getters and setters from the prototype. This
+ // means we have to implement them again, here in the subclass.
+ // FIXME: Use ES6 classes!
+
+ get scrubbing()
+ {
+ return Object.getOwnPropertyDescriptor(Controller.prototype, "scrubbing").get.call(this);
+ },
+
+ set scrubbing(flag)
+ {
+ Object.getOwnPropertyDescriptor(Controller.prototype, "scrubbing").set.call(this, flag);
+ },
+
+ get pageScaleFactor()
+ {
+ return this._pageScaleFactor;
+ },
+
+ set pageScaleFactor(newScaleFactor)
+ {
+ if (!newScaleFactor || this._pageScaleFactor === newScaleFactor)
+ return;
+
+ this._pageScaleFactor = newScaleFactor;
+
+ var scaleValue = 1 / newScaleFactor;
+ var scaleTransform = "scale(" + scaleValue + ")";
+
+ function applyScaleFactorToElement(element) {
+ if (scaleValue > 1) {
+ element.style.zoom = scaleValue;
+ element.style.webkitTransform = "scale(1)";
+ } else {
+ element.style.zoom = 1;
+ element.style.webkitTransform = scaleTransform;
+ }
+ }
+
+ if (this.controls.startPlaybackButton)
+ applyScaleFactorToElement(this.controls.startPlaybackButton);
+ if (this.controls.panel) {
+ applyScaleFactorToElement(this.controls.panel);
+ if (scaleValue > 1) {
+ this.controls.panel.style.width = "100%";
+ this.controls.timelineBox.style.webkitTextSizeAdjust = (100 * scaleValue) + "%";
+ } else {
+ var bottomAligment = -2 * scaleValue;
+ this.controls.panel.style.bottom = bottomAligment + "px";
+ this.controls.panel.style.paddingBottom = -(newScaleFactor * bottomAligment) + "px";
+ this.controls.panel.style.width = Math.round(newScaleFactor * 100) + "%";
+ this.controls.timelineBox.style.webkitTextSizeAdjust = "auto";
+ }
+ this.controls.panelBackground.style.height = (50 * scaleValue) + "px";
+
+ this.setNeedsTimelineMetricsUpdate();
+ this.updateProgress();
+ this.scheduleUpdateLayoutForDisplayedWidth();
+ }
+ },
+
+};
+
+Object.create(Controller.prototype).extend(ControllerIOS.prototype);
+Object.defineProperty(ControllerIOS.prototype, 'constructor', { enumerable: false, value: ControllerIOS });