Implement MediaController.
https://bugs.webkit.org/show_bug.cgi?id=71408

Reviewed by Eric Carlson.

Source/JavaScriptCore:

Change the definition of WTF_USE_COREAUDIO to exclude Windows completely, as
CoreAudioClock.h is not available there.

* wtf/Platform.h:

Source/WebCore:

Tests: media/media-controller-playback.html
       media/media-controller.html

Adds support for the MediaController DOM object, and the mediagroup and mediacontroller
HTMLMediaElement attributes.

MediaController is an DOM object which synchronizes playback of multiple HTMLMediaElements. It can
either be created by a page script and assigned to a HTMLMediaElement using the controller property,
or all HTMLMediaElements with identical mediagroup attributes will have a MediaController assigned
automatically.

Add an abstract interface implemented by both MediaController and HTMLMediaElement.
* html/MediaControllerInterface.h: Added.
(WebCore::MediaControllerInterface::~MediaControllerInterface):

Add the MediaController object and IDL.
* html/MediaController.cpp: Added.
(mediaGroupToMediaControllerMap):
(MediaController::mediaControllerForMediaGroup):
(MediaController::create):
(MediaController::MediaController):
(MediaController::~MediaController):
(MediaController::addMediaElement):
(MediaController::removeMediaElement):
(MediaController::containsMediaElement):
(MediaController::buffered):
(MediaController::seekable):
(MediaController::played):
(MediaController::duration):
(MediaController::currentTime):
(MediaController::setCurrentTime):
(MediaController::play):
(MediaController::pause):
(MediaController::setDefaultPlaybackRate):
(MediaController::setPlaybackRate):
(MediaController::setVolume):
(MediaController::setMuted):
(MediaController::reportControllerState):
(MediaController::updateReadyState):
(MediaController::updatePlaybackState):
(MediaController::updateMediaElements):
(MediaController::scheduleEvent):
(MediaController::asyncEventTimerFired):
(MediaController::scriptExecutionContext):
(MediaController::hasAudio):
(MediaController::hasVideo):
(MediaController::hasClosedCaptions):
(MediaController::setClosedCaptionsVisible):
(MediaController::supportsScanning):
(MediaController::beginScrubbing):
(MediaController::endScrubbing):
(MediaController::canPlay):
(MediaController::isLiveStream):
(MediaController::hasSource):
(MediaController::returnToRealtime):
(MediaController::isBlocked):
(MediaController::hasEnded):
* html/MediaController.h: Added.
(WebCore::MediaController::mediaGroup):
(WebCore::MediaController::paused):
(WebCore::MediaController::defaultPlaybackRate):
(WebCore::MediaController::playbackRate):
(WebCore::MediaController::volume):
(WebCore::MediaController::muted):
(WebCore::MediaController::readyState):
(WebCore::MediaController::playbackState):
(WebCore::MediaController::supportsFullscreen):
(WebCore::MediaController::isFullscreen):
(WebCore::MediaController::enterFullscreen):
(WebCore::MediaController::closedCaptionsVisible):
(WebCore::MediaController::refEventTarget):
(WebCore::MediaController::derefEventTarget):
(WebCore::MediaController::toMediaController):
(WebCore::MediaController::eventTargetData):
(WebCore::MediaController::ensureEventTargetData):
* html/MediaController.idl: Added.

Add convenience functions to TimeRanges which can calculate intersections and
unions between TimeRanges objects.
* html/TimeRanges.cpp:
(TimeRanges::copy):
(TimeRanges::invert):
(TimeRanges::intersectWith):
(TimeRanges::unionWith):
* html/TimeRanges.h:

Add MediaControllerConstructor to the Window object.
* page/DOMWindow.idl:

Add the two new attribute names, mediagroup and controller.
* html/HTMLAttributeNames.in:

Add support for the new attributes, and add overridden behavior when a media element
has a current media controller:
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::~HTMLMediaElement):
(WebCore::HTMLMediaElement::parseMappedAttribute):
(WebCore::HTMLMediaElement::prepareForLoad):
(WebCore::HTMLMediaElement::setReadyState):
(WebCore::HTMLMediaElement::setCurrentTime):
(WebCore::HTMLMediaElement::setPlaybackRate):
(WebCore::HTMLMediaElement::playInternal):
(WebCore::HTMLMediaElement::togglePlayState):
(WebCore::HTMLMediaElement::mediaPlayerTimeChanged):
(WebCore::HTMLMediaElement::seekable):
(WebCore::HTMLMediaElement::potentiallyPlaying):
(WebCore::HTMLMediaElement::endedPlayback):
(WebCore::HTMLMediaElement::updateVolume):
(WebCore::HTMLMediaElement::updatePlayState):
(WebCore::HTMLMediaElement::userCancelledLoad):
(WebCore::HTMLMediaElement::mediaGroup):
(WebCore::HTMLMediaElement::setMediaGroup):
(WebCore::HTMLMediaElement::controller):
(WebCore::HTMLMediaElement::setController):
(WebCore::HTMLMediaElement::updateMediaController):
(WebCore::HTMLMediaElement::isBlockedOnMediaController):
* html/HTMLMediaElement.h:
(WebCore::HTMLMediaElement::hasSource):
(WebCore::HTMLMediaElement::isLiveStream):
* html/HTMLMediaElement.idl:
* bindings/js/JSHTMLMediaElementCustom.cpp: Added.
(WebCore::JSHTMLMediaElement::setMediaController):

Add convenience functions to store a mapping of media-elements-per-document to allow
a quick lookup of media elements with the same media group within a given document:
* html/HTMLMediaElement.cpp:
(WebCore::documentToElementSetMap):
(WebCore::addElementToDocumentMap):
(WebCore::removeElementFromDocumentMap):

Add a function "seekable" which returns a TimeRanges containing the seekable time ranges
in a media element.  By default this is a single range of [0, maxTimeSeekable].
* platform/graphics/MediaPlayer.cpp:
(WebCore::MediaPlayer::seekable):
* platform/graphics/MediaPlayer.h:
* platform/graphics/MediaPlayerPrivate.h:
(WebCore::MediaPlayerPrivateInterface::seekable):

Support functions to cast between MediaController and EventTarget.
* bindings/js/JSEventTarget.cpp:
(WebCore::toJS):
* dom/EventTarget.cpp:
(WebCore::EventTarget::toMediaController):
* dom/EventTarget.h:

Fixed an infinite-recursion bug due to a collision between WTF::currentTime and
ClockGeneric::currentTime:
* platform/ClockGeneric.cpp:
(ClockGeneric::ClockGeneric):
(ClockGeneric::setCurrentTime):
(ClockGeneric::currentTime):
(ClockGeneric::setPlayRate):
(ClockGeneric::start):
(ClockGeneric::stop):
(ClockGeneric::now):
* platform/ClockGeneric.h:

Boilerplate to support creating the derived sources for MediaController and adding new sources
to the project:
* CMakeLists.txt:
* DerivedSources.cpp:
* DerivedSources.make:
* GNUmakefile.list.am:
* WebCore.gypi:
* WebCore.xcodeproj/project.pbxproj:

LayoutTests:

* media/media-controller-expected.txt: Added.
* media/media-controller-playback-expected.txt: Added.
* media/media-controller-playback.html: Added.
* media/media-controller.html: Added.
* platform/mac/fast/dom/Window/window-properties-expected.txt:
* platform/mac/fast/js/global-constructors-expected.txt:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@100159 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/CMakeLists.txt b/Source/WebCore/CMakeLists.txt
index e58673f..f0524b2 100644
--- a/Source/WebCore/CMakeLists.txt
+++ b/Source/WebCore/CMakeLists.txt
@@ -276,6 +276,7 @@
     html/HTMLUnknownElement.idl
     html/HTMLVideoElement.idl
     html/ImageData.idl
+    html/MediaController.idl
     html/MediaError.idl
     html/TextMetrics.idl
     html/TextTrack.idl
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 7586474..33c0444 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,176 @@
+2011-11-08  Jer Noble  <jer.noble@apple.com>
+
+        Implement MediaController.
+        https://bugs.webkit.org/show_bug.cgi?id=71408
+
+        Reviewed by Eric Carlson.
+
+        Tests: media/media-controller-playback.html
+               media/media-controller.html
+
+        Adds support for the MediaController DOM object, and the mediagroup and mediacontroller
+        HTMLMediaElement attributes.
+
+        MediaController is an DOM object which synchronizes playback of multiple HTMLMediaElements. It can
+        either be created by a page script and assigned to a HTMLMediaElement using the controller property,
+        or all HTMLMediaElements with identical mediagroup attributes will have a MediaController assigned 
+        automatically.
+
+        Add an abstract interface implemented by both MediaController and HTMLMediaElement.
+        * html/MediaControllerInterface.h: Added.
+        (WebCore::MediaControllerInterface::~MediaControllerInterface):
+
+        Add the MediaController object and IDL.
+        * html/MediaController.cpp: Added.
+        (mediaGroupToMediaControllerMap):
+        (MediaController::mediaControllerForMediaGroup):
+        (MediaController::create):
+        (MediaController::MediaController):
+        (MediaController::~MediaController):
+        (MediaController::addMediaElement):
+        (MediaController::removeMediaElement):
+        (MediaController::containsMediaElement):
+        (MediaController::buffered):
+        (MediaController::seekable):
+        (MediaController::played):
+        (MediaController::duration):
+        (MediaController::currentTime):
+        (MediaController::setCurrentTime):
+        (MediaController::play):
+        (MediaController::pause):
+        (MediaController::setDefaultPlaybackRate):
+        (MediaController::setPlaybackRate):
+        (MediaController::setVolume):
+        (MediaController::setMuted):
+        (MediaController::reportControllerState):
+        (MediaController::updateReadyState):
+        (MediaController::updatePlaybackState):
+        (MediaController::updateMediaElements):
+        (MediaController::scheduleEvent):
+        (MediaController::asyncEventTimerFired):
+        (MediaController::scriptExecutionContext):
+        (MediaController::hasAudio):
+        (MediaController::hasVideo):
+        (MediaController::hasClosedCaptions):
+        (MediaController::setClosedCaptionsVisible):
+        (MediaController::supportsScanning):
+        (MediaController::beginScrubbing):
+        (MediaController::endScrubbing):
+        (MediaController::canPlay):
+        (MediaController::isLiveStream):
+        (MediaController::hasSource):
+        (MediaController::returnToRealtime):
+        (MediaController::isBlocked):
+        (MediaController::hasEnded):
+        * html/MediaController.h: Added.
+        (WebCore::MediaController::mediaGroup):
+        (WebCore::MediaController::paused):
+        (WebCore::MediaController::defaultPlaybackRate):
+        (WebCore::MediaController::playbackRate):
+        (WebCore::MediaController::volume):
+        (WebCore::MediaController::muted):
+        (WebCore::MediaController::readyState):
+        (WebCore::MediaController::playbackState):
+        (WebCore::MediaController::supportsFullscreen):
+        (WebCore::MediaController::isFullscreen):
+        (WebCore::MediaController::enterFullscreen):
+        (WebCore::MediaController::closedCaptionsVisible):
+        (WebCore::MediaController::refEventTarget):
+        (WebCore::MediaController::derefEventTarget):
+        (WebCore::MediaController::toMediaController):
+        (WebCore::MediaController::eventTargetData):
+        (WebCore::MediaController::ensureEventTargetData):
+        * html/MediaController.idl: Added.
+
+        Add convenience functions to TimeRanges which can calculate intersections and
+        unions between TimeRanges objects.
+        * html/TimeRanges.cpp:
+        (TimeRanges::copy):
+        (TimeRanges::invert):
+        (TimeRanges::intersectWith):
+        (TimeRanges::unionWith):
+        * html/TimeRanges.h:
+
+        Add MediaControllerConstructor to the Window object.
+        * page/DOMWindow.idl:
+
+        Add the two new attribute names, mediagroup and controller.
+        * html/HTMLAttributeNames.in:
+
+        Add support for the new attributes, and add overridden behavior when a media element
+        has a current media controller:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::~HTMLMediaElement):
+        (WebCore::HTMLMediaElement::parseMappedAttribute):
+        (WebCore::HTMLMediaElement::prepareForLoad):
+        (WebCore::HTMLMediaElement::setReadyState):
+        (WebCore::HTMLMediaElement::setCurrentTime):
+        (WebCore::HTMLMediaElement::setPlaybackRate):
+        (WebCore::HTMLMediaElement::playInternal):
+        (WebCore::HTMLMediaElement::togglePlayState):
+        (WebCore::HTMLMediaElement::mediaPlayerTimeChanged):
+        (WebCore::HTMLMediaElement::seekable):
+        (WebCore::HTMLMediaElement::potentiallyPlaying):
+        (WebCore::HTMLMediaElement::endedPlayback):
+        (WebCore::HTMLMediaElement::updateVolume):
+        (WebCore::HTMLMediaElement::updatePlayState):
+        (WebCore::HTMLMediaElement::userCancelledLoad):
+        (WebCore::HTMLMediaElement::mediaGroup):
+        (WebCore::HTMLMediaElement::setMediaGroup):
+        (WebCore::HTMLMediaElement::controller):
+        (WebCore::HTMLMediaElement::setController):
+        (WebCore::HTMLMediaElement::updateMediaController):
+        (WebCore::HTMLMediaElement::isBlockedOnMediaController):
+        * html/HTMLMediaElement.h:
+        (WebCore::HTMLMediaElement::hasSource):
+        (WebCore::HTMLMediaElement::isLiveStream):
+        * html/HTMLMediaElement.idl:
+        * bindings/js/JSHTMLMediaElementCustom.cpp: Added.
+        (WebCore::JSHTMLMediaElement::setMediaController):
+
+        Add convenience functions to store a mapping of media-elements-per-document to allow
+        a quick lookup of media elements with the same media group within a given document:
+        * html/HTMLMediaElement.cpp:
+        (WebCore::documentToElementSetMap):
+        (WebCore::addElementToDocumentMap):
+        (WebCore::removeElementFromDocumentMap):
+        
+        Add a function "seekable" which returns a TimeRanges containing the seekable time ranges
+        in a media element.  By default this is a single range of [0, maxTimeSeekable].
+        * platform/graphics/MediaPlayer.cpp:
+        (WebCore::MediaPlayer::seekable):
+        * platform/graphics/MediaPlayer.h:
+        * platform/graphics/MediaPlayerPrivate.h:
+        (WebCore::MediaPlayerPrivateInterface::seekable):
+
+        Support functions to cast between MediaController and EventTarget.
+        * bindings/js/JSEventTarget.cpp:
+        (WebCore::toJS):
+        * dom/EventTarget.cpp:
+        (WebCore::EventTarget::toMediaController):
+        * dom/EventTarget.h:
+
+        Fixed an infinite-recursion bug due to a collision between WTF::currentTime and
+        ClockGeneric::currentTime:
+        * platform/ClockGeneric.cpp:
+        (ClockGeneric::ClockGeneric):
+        (ClockGeneric::setCurrentTime):
+        (ClockGeneric::currentTime):
+        (ClockGeneric::setPlayRate):
+        (ClockGeneric::start):
+        (ClockGeneric::stop):
+        (ClockGeneric::now):
+        * platform/ClockGeneric.h:
+
+        Boilerplate to support creating the derived sources for MediaController and adding new sources
+        to the project:
+        * CMakeLists.txt:
+        * DerivedSources.cpp:
+        * DerivedSources.make:
+        * GNUmakefile.list.am:
+        * WebCore.gypi:
+        * WebCore.xcodeproj/project.pbxproj:
+
 2011-11-14  Florin Malita  <fmalita@google.com>
 
         Multiple foreign objects not rendered
diff --git a/Source/WebCore/DerivedSources.cpp b/Source/WebCore/DerivedSources.cpp
index b2e4529..886925f 100644
--- a/Source/WebCore/DerivedSources.cpp
+++ b/Source/WebCore/DerivedSources.cpp
@@ -228,6 +228,7 @@
 #include "JSJavaScriptCallFrame.cpp"
 #include "JSKeyboardEvent.cpp"
 #include "JSLocation.cpp"
+#include "JSMediaController.cpp"
 #include "JSMediaError.cpp"
 #include "JSMediaList.cpp"
 #include "JSMediaQueryList.cpp"
diff --git a/Source/WebCore/DerivedSources.make b/Source/WebCore/DerivedSources.make
index b8aa6a4..cec4242 100644
--- a/Source/WebCore/DerivedSources.make
+++ b/Source/WebCore/DerivedSources.make
@@ -277,6 +277,7 @@
     KeyboardEvent \
     Location \
     LowPass2FilterNode \
+    MediaController \
     MediaElementAudioSourceNode \
     MediaError \
     MediaList \
diff --git a/Source/WebCore/DerivedSources.pri b/Source/WebCore/DerivedSources.pri
index 109b4b6..6158b47 100644
--- a/Source/WebCore/DerivedSources.pri
+++ b/Source/WebCore/DerivedSources.pri
@@ -314,6 +314,7 @@
     html/HTMLUnknownElement.idl \
     html/HTMLVideoElement.idl \
     html/ImageData.idl \
+    html/MediaController.idl \
     html/MediaError.idl \
     html/TextMetrics.idl \
     html/TimeRanges.idl \
diff --git a/Source/WebCore/GNUmakefile.list.am b/Source/WebCore/GNUmakefile.list.am
index 155111d..0d2133c 100644
--- a/Source/WebCore/GNUmakefile.list.am
+++ b/Source/WebCore/GNUmakefile.list.am
@@ -359,6 +359,8 @@
 	DerivedSources/WebCore/JSLocalMediaStream.h \
 	DerivedSources/WebCore/JSLocation.cpp \
 	DerivedSources/WebCore/JSLocation.h \
+	DerivedSources/WebCore/JSMediaController.cpp \
+	DerivedSources/WebCore/JSMediaController.h \
 	DerivedSources/WebCore/JSMediaError.cpp \
 	DerivedSources/WebCore/JSMediaError.h \
 	DerivedSources/WebCore/JSMediaList.cpp \
@@ -791,6 +793,7 @@
 	Source/WebCore/bindings/js/JSHTMLInputElementCustom.cpp \
 	Source/WebCore/bindings/js/JSHTMLInputElementCustom.h \
 	Source/WebCore/bindings/js/JSHTMLLinkElementCustom.cpp \
+	Source/WebCore/bindings/js/JSHTMLMediaElementCustom.cpp \
 	Source/WebCore/bindings/js/JSHTMLObjectElementCustom.cpp \
 	Source/WebCore/bindings/js/JSHTMLObjectElementCustom.h \
 	Source/WebCore/bindings/js/JSHTMLOptionsCollectionCustom.cpp \
@@ -1878,6 +1881,8 @@
 	Source/WebCore/html/LinkRelAttribute.h \
 	Source/WebCore/html/LoadableTextTrack.cpp \
 	Source/WebCore/html/LoadableTextTrack.h \
+	Source/WebCore/html/MediaController.cpp \
+	Source/WebCore/html/MediaController.h \
 	Source/WebCore/html/MediaDocument.cpp \
 	Source/WebCore/html/MediaDocument.h \
 	Source/WebCore/html/MediaError.h \
@@ -2461,6 +2466,10 @@
 	Source/WebCore/platform/AsyncFileSystem.cpp \
 	Source/WebCore/platform/AsyncFileSystem.h \
 	Source/WebCore/platform/AutodrainedPool.h \
+	Source/WebCore/platform/Clock.cpp \
+	Source/WebCore/platform/Clock.h \
+	Source/WebCore/platform/ClockGeneric.cpp \
+	Source/WebCore/platform/ClockGeneric.h \
 	Source/WebCore/platform/ColorChooser.cpp \
 	Source/WebCore/platform/ColorChooser.h \
 	Source/WebCore/platform/ContentType.cpp \
diff --git a/Source/WebCore/Target.pri b/Source/WebCore/Target.pri
index c04f280..d1d5c84 100644
--- a/Source/WebCore/Target.pri
+++ b/Source/WebCore/Target.pri
@@ -188,6 +188,7 @@
         bindings/v8/custom/V8HTMLImageElementConstructor.cpp \
         bindings/v8/custom/V8HTMLInputElementCustom.cpp \
         bindings/v8/custom/V8HTMLLinkElementCustom.cpp \
+        bindings/v8/custom/V8HTMLMediaElementCustom.cpp \
         bindings/v8/custom/V8HTMLOptionsCollectionCustom.cpp \
         bindings/v8/custom/V8HTMLOutputElementCustom.cpp \
         bindings/v8/custom/V8HTMLPlugInElementCustom.cpp \
@@ -298,6 +299,7 @@
         bindings/js/JSHTMLFrameSetElementCustom.cpp \
         bindings/js/JSHTMLInputElementCustom.cpp \
         bindings/js/JSHTMLLinkElementCustom.cpp \
+        bindings/js/JSHTMLMediaElementCustom.cpp \
         bindings/js/JSHTMLObjectElementCustom.cpp \
         bindings/js/JSHTMLOptionsCollectionCustom.cpp \
         bindings/js/JSHTMLOutputElementCustom.cpp \
@@ -1014,6 +1016,8 @@
     platform/text/LocalizedDateNone.cpp \
     platform/text/LocalizedNumberNone.cpp \
     platform/text/QuotedPrintable.cpp \
+    platform/Clock.cpp \
+    platform/ClockGeneric.cpp \
     platform/ContentType.cpp \
     platform/CrossThreadCopier.cpp \
     platform/DateComponents.cpp \
@@ -1024,7 +1028,6 @@
     platform/FileIconLoader.cpp \
     platform/FileStream.cpp \
     platform/FileSystem.cpp \
-    platform/ClockGeneric.cpp \
     platform/GeolocationService.cpp \
     platform/image-decoders/qt/ImageFrameQt.cpp \
     platform/graphics/FontDescription.cpp \
@@ -1858,6 +1861,7 @@
     html/LabelsNodeList.h \
     html/LinkRelAttribute.h \
     html/LoadableTextTrack.h \
+    html/MediaController.h \
     html/MediaDocument.h \
     html/MicroDataItemValue.h \
     html/PluginDocument.h \
@@ -2046,6 +2050,8 @@
     platform/animation/AnimationList.h \
     platform/Arena.h \
     platform/AsyncFileStream.h \
+    platform/Clock.h \
+    platform/ClockGeneric.h \
     platform/ContentType.h \
     platform/ContextMenu.h \
     platform/CrossThreadCopier.h \
@@ -2064,7 +2070,6 @@
     platform/mock/GeolocationServiceMock.h \
     platform/mock/SpeechInputClientMock.h \
     platform/mock/ScrollbarThemeMock.h \
-    platform/ClockGeneric.h \
     platform/graphics/BitmapImage.h \
     platform/graphics/Color.h \
     platform/graphics/filters/FEBlend.h \
@@ -3088,6 +3093,7 @@
         html/HTMLMediaElement.cpp \
         html/HTMLSourceElement.cpp \
         html/HTMLVideoElement.cpp \
+        html/MediaController.cpp \
         html/shadow/MediaControlElements.cpp \
         html/TimeRanges.cpp \
         platform/graphics/MediaPlayer.cpp \
diff --git a/Source/WebCore/WebCore.gypi b/Source/WebCore/WebCore.gypi
index 29b72ec..48168c3 100644
--- a/Source/WebCore/WebCore.gypi
+++ b/Source/WebCore/WebCore.gypi
@@ -1331,6 +1331,7 @@
             'html/HTMLUnknownElement.idl',
             'html/HTMLVideoElement.idl',
             'html/ImageData.idl',
+            'html/MediaController.idl',
             'html/MediaError.idl',
             'html/TextMetrics.idl',
             'html/TextTrack.idl',
@@ -1877,6 +1878,7 @@
             'bindings/js/JSHTMLInputElementCustom.cpp',
             'bindings/js/JSHTMLInputElementCustom.h',
             'bindings/js/JSHTMLLinkElementCustom.cpp',
+            'bindings/js/JSHTMLMediaElementCustom.cpp',
             'bindings/js/JSHTMLObjectElementCustom.cpp',
             'bindings/js/JSHTMLObjectElementCustom.h',
             'bindings/js/JSHTMLOptionsCollectionCustom.cpp',
@@ -2199,6 +2201,7 @@
             'bindings/v8/custom/V8HTMLImageElementConstructor.h',
             'bindings/v8/custom/V8HTMLInputElementCustom.cpp',
             'bindings/v8/custom/V8HTMLLinkElementCustom.cpp',
+            'bindings/v8/custom/V8HTMLMediaElementCustom.cpp',
             'bindings/v8/custom/V8HTMLOptionsCollectionCustom.cpp',
             'bindings/v8/custom/V8HTMLOutputElementCustom.cpp',
             'bindings/v8/custom/V8HTMLPlugInElementCustom.cpp',
@@ -5606,6 +5609,8 @@
             'html/LinkRelAttribute.h',
             'html/LoadableTextTrack.cpp',
             'html/LoadableTextTrack.h',
+            'html/MediaController.cpp',
+            'html/MediaController.h',
             'html/MediaDocument.cpp',
             'html/MediaDocument.h',
             'html/MediaError.h',
diff --git a/Source/WebCore/WebCore.vcproj/WebCore.vcproj b/Source/WebCore/WebCore.vcproj/WebCore.vcproj
index 4d403b4..3c61fb7 100755
--- a/Source/WebCore/WebCore.vcproj/WebCore.vcproj
+++ b/Source/WebCore/WebCore.vcproj/WebCore.vcproj
@@ -26262,6 +26262,22 @@
 				>
 			</File>
 			<File
+				RelativePath="..\platform\Clock.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\platform\Clock.h"
+				>
+			</File>
+			<File
+				RelativePath="..\platform\ClockGeneric.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\platform\ClockGeneric.h"
+				>
+			</File>
+			<File
 				RelativePath="..\platform\ContentType.cpp"
 				>
 			</File>
@@ -57718,6 +57734,14 @@
 				>
 			</File>
 			<File
+				RelativePath="..\html\MediaController.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\html\MediaController.h"
+				>
+			</File>
+			<File
 				RelativePath="..\html\MediaDocument.cpp"
 				>
 			</File>
diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
index 2a20c89..f23b170 100644
--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj
+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
@@ -5720,17 +5720,22 @@
 		CD0DBFEE1422768500280263 /* IRC_Composite_C_R0195_T345_P315.wav in Copy Audio Resources */ = {isa = PBXBuildFile; fileRef = CD0DBE0D1422759500280263 /* IRC_Composite_C_R0195_T345_P315.wav */; };
 		CD0DBFEF1422768500280263 /* IRC_Composite_C_R0195_T345_P330.wav in Copy Audio Resources */ = {isa = PBXBuildFile; fileRef = CD0DBE0E1422759500280263 /* IRC_Composite_C_R0195_T345_P330.wav */; };
 		CD0DBFF01422768500280263 /* IRC_Composite_C_R0195_T345_P345.wav in Copy Audio Resources */ = {isa = PBXBuildFile; fileRef = CD0DBE0F1422759500280263 /* IRC_Composite_C_R0195_T345_P345.wav */; };
+		CD27F6E51457685A0078207D /* JSMediaController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD27F6E2145767580078207D /* JSMediaController.cpp */; };
+		CD27F6E7145770D30078207D /* MediaController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CD27F6E6145770D30078207D /* MediaController.cpp */; };
 		CD82030A1395AB6A00F956C6 /* WebVideoFullscreenController.h in Headers */ = {isa = PBXBuildFile; fileRef = CD8203061395AB6A00F956C6 /* WebVideoFullscreenController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		CD82030B1395AB6A00F956C6 /* WebVideoFullscreenController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CD8203071395AB6A00F956C6 /* WebVideoFullscreenController.mm */; settings = {COMPILER_FLAGS = "-Wno-undef"; }; };
 		CD82030C1395AB6A00F956C6 /* WebVideoFullscreenHUDWindowController.h in Headers */ = {isa = PBXBuildFile; fileRef = CD8203081395AB6A00F956C6 /* WebVideoFullscreenHUDWindowController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		CD82030D1395AB6A00F956C6 /* WebVideoFullscreenHUDWindowController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CD8203091395AB6A00F956C6 /* WebVideoFullscreenHUDWindowController.mm */; };
 		CD8203101395ACE700F956C6 /* WebWindowAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = CD82030E1395ACE700F956C6 /* WebWindowAnimation.h */; };
 		CD8203111395ACE700F956C6 /* WebWindowAnimation.mm in Sources */ = {isa = PBXBuildFile; fileRef = CD82030F1395ACE700F956C6 /* WebWindowAnimation.mm */; };
+		CDD525D7145B6DD0008D204D /* JSHTMLMediaElementCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDF65CCC145B6AFE00C4C7AA /* JSHTMLMediaElementCustom.cpp */; };
 		CDEA763014608A53008B31F1 /* PlatformClockCA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDEA762E146084DE008B31F1 /* PlatformClockCA.cpp */; };
 		CDEA76341460B56F008B31F1 /* ClockGeneric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDEA76321460AE29008B31F1 /* ClockGeneric.cpp */; };
 		CDEA76351460B71A008B31F1 /* Clock.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDEA76331460B462008B31F1 /* Clock.cpp */; };
 		CDEA7C841276230400B846DD /* RenderFullScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = CDEA7C821276230400B846DD /* RenderFullScreen.h */; };
 		CDEA7C851276230400B846DD /* RenderFullScreen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDEA7C831276230400B846DD /* RenderFullScreen.cpp */; };
+		CDF65CC8145B1E7500C4C7AA /* MediaController.h in Headers */ = {isa = PBXBuildFile; fileRef = CD27F6E4145767870078207D /* MediaController.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		CDF65CCA145B448800C4C7AA /* MediaControllerInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = CDF65CC9145B43A700C4C7AA /* MediaControllerInterface.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		CE02F0C411E83ADD00C6684A /* ScriptControllerBase.h in Headers */ = {isa = PBXBuildFile; fileRef = CE02F0C311E83ADD00C6684A /* ScriptControllerBase.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		CE057FA51220731100A476D5 /* DocumentMarkerController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CE057FA31220731100A476D5 /* DocumentMarkerController.cpp */; };
 		CE057FA61220731100A476D5 /* DocumentMarkerController.h in Headers */ = {isa = PBXBuildFile; fileRef = CE057FA41220731100A476D5 /* DocumentMarkerController.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -13040,6 +13045,11 @@
 		CD0DBE0D1422759500280263 /* IRC_Composite_C_R0195_T345_P315.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = IRC_Composite_C_R0195_T345_P315.wav; path = platform/audio/resources/IRC_Composite_C_R0195_T345_P315.wav; sourceTree = SOURCE_ROOT; };
 		CD0DBE0E1422759500280263 /* IRC_Composite_C_R0195_T345_P330.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = IRC_Composite_C_R0195_T345_P330.wav; path = platform/audio/resources/IRC_Composite_C_R0195_T345_P330.wav; sourceTree = SOURCE_ROOT; };
 		CD0DBE0F1422759500280263 /* IRC_Composite_C_R0195_T345_P345.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = IRC_Composite_C_R0195_T345_P345.wav; path = platform/audio/resources/IRC_Composite_C_R0195_T345_P345.wav; sourceTree = SOURCE_ROOT; };
+		CD27F6E014575C1B0078207D /* MediaController.idl */ = {isa = PBXFileReference; lastKnownFileType = text; path = MediaController.idl; sourceTree = "<group>"; };
+		CD27F6E2145767580078207D /* JSMediaController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSMediaController.cpp; sourceTree = "<group>"; };
+		CD27F6E3145767580078207D /* JSMediaController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSMediaController.h; sourceTree = "<group>"; };
+		CD27F6E4145767870078207D /* MediaController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaController.h; sourceTree = "<group>"; };
+		CD27F6E6145770D30078207D /* MediaController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MediaController.cpp; sourceTree = "<group>"; };
 		CD4E0AFA11F7BC27009D3811 /* fullscreen.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = fullscreen.css; sourceTree = "<group>"; };
 		CD8203061395AB6A00F956C6 /* WebVideoFullscreenController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebVideoFullscreenController.h; sourceTree = "<group>"; };
 		CD8203071395AB6A00F956C6 /* WebVideoFullscreenController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebVideoFullscreenController.mm; sourceTree = "<group>"; };
@@ -13048,6 +13058,8 @@
 		CD82030E1395ACE700F956C6 /* WebWindowAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebWindowAnimation.h; sourceTree = "<group>"; };
 		CD82030F1395ACE700F956C6 /* WebWindowAnimation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WebWindowAnimation.mm; sourceTree = "<group>"; };
 		CDBD93BA1333BD4B002570E3 /* fullscreenQuickTime.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = fullscreenQuickTime.css; sourceTree = "<group>"; };
+		CDCE5CCF14633BC900D47CCA /* EventFactory.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EventFactory.in; sourceTree = "<group>"; };
+		CDCE5CD014633BC900D47CCA /* EventTargetFactory.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EventTargetFactory.in; sourceTree = "<group>"; };
 		CDEA762C14608224008B31F1 /* Clock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Clock.h; sourceTree = "<group>"; };
 		CDEA762E146084DE008B31F1 /* PlatformClockCA.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlatformClockCA.cpp; sourceTree = "<group>"; };
 		CDEA762F146084EE008B31F1 /* PlatformClockCA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlatformClockCA.h; sourceTree = "<group>"; };
@@ -13056,6 +13068,8 @@
 		CDEA76331460B462008B31F1 /* Clock.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Clock.cpp; sourceTree = "<group>"; };
 		CDEA7C821276230400B846DD /* RenderFullScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderFullScreen.h; sourceTree = "<group>"; };
 		CDEA7C831276230400B846DD /* RenderFullScreen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderFullScreen.cpp; sourceTree = "<group>"; };
+		CDF65CC9145B43A700C4C7AA /* MediaControllerInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaControllerInterface.h; sourceTree = "<group>"; };
+		CDF65CCC145B6AFE00C4C7AA /* JSHTMLMediaElementCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSHTMLMediaElementCustom.cpp; sourceTree = "<group>"; };
 		CE02F0C311E83ADD00C6684A /* ScriptControllerBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScriptControllerBase.h; sourceTree = "<group>"; };
 		CE057FA31220731100A476D5 /* DocumentMarkerController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DocumentMarkerController.cpp; sourceTree = "<group>"; };
 		CE057FA41220731100A476D5 /* DocumentMarkerController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DocumentMarkerController.h; sourceTree = "<group>"; };
@@ -17398,6 +17412,10 @@
 				E44613A00CD6331000FADA75 /* VoidCallback.idl */,
 				F55B3DAB1251F12D003EF269 /* WeekInputType.cpp */,
 				F55B3DAC1251F12D003EF269 /* WeekInputType.h */,
+				CD27F6E014575C1B0078207D /* MediaController.idl */,
+				CD27F6E4145767870078207D /* MediaController.h */,
+				CD27F6E6145770D30078207D /* MediaController.cpp */,
+				CDF65CC9145B43A700C4C7AA /* MediaControllerInterface.h */,
 			);
 			path = html;
 			sourceTree = "<group>";
@@ -17631,6 +17649,8 @@
 		A83B79080CCAFF2B000B0825 /* HTML */ = {
 			isa = PBXGroup;
 			children = (
+				CD27F6E2145767580078207D /* JSMediaController.cpp */,
+				CD27F6E3145767580078207D /* JSMediaController.h */,
 				49EECEF2105070C400099FAB /* JSArrayBuffer.cpp */,
 				49EECEF3105070C400099FAB /* JSArrayBuffer.h */,
 				49EECF19105072F300099FAB /* JSArrayBufferView.cpp */,
@@ -19657,6 +19677,7 @@
 				BCC438770E886CC700533DD5 /* JSHTMLInputElementCustom.cpp */,
 				E1AD14221295EA7F00ACA989 /* JSHTMLInputElementCustom.h */,
 				E1AD139A1295D92600ACA989 /* JSHTMLLinkElementCustom.cpp */,
+				CDF65CCC145B6AFE00C4C7AA /* JSHTMLMediaElementCustom.cpp */,
 				BC305CA30C0781BB00CD20F0 /* JSHTMLObjectElementCustom.cpp */,
 				E1AD14241295EA9500ACA989 /* JSHTMLObjectElementCustom.h */,
 				448AD27A0A4813790023D179 /* JSHTMLOptionsCollectionCustom.cpp */,
@@ -20974,6 +20995,8 @@
 		F523D32402DE4478018635CA /* dom */ = {
 			isa = PBXGroup;
 			children = (
+				CDCE5CCF14633BC900D47CCA /* EventFactory.in */,
+				CDCE5CD014633BC900D47CCA /* EventTargetFactory.in */,
 				E1C4DE6D0EA75C650023CCD6 /* ActiveDOMObject.cpp */,
 				E1C4DE680EA75C1E0023CCD6 /* ActiveDOMObject.h */,
 				A8C4A7FC09D563270003AC8D /* Attr.cpp */,
@@ -24673,6 +24696,8 @@
 				71537A01146BD9D7008BD615 /* SVGPathData.h in Headers */,
 				31313F661443B35F006E2A90 /* FilterEffectRenderer.h in Headers */,
 				29CD61DE146D02890068E82A /* WebKitCSSShaderValue.h in Headers */,
+				CDF65CC8145B1E7500C4C7AA /* MediaController.h in Headers */,
+				CDF65CCA145B448800C4C7AA /* MediaControllerInterface.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -27531,6 +27556,9 @@
 				CAE9F90F146441F000C245B0 /* CSSAspectRatioValue.cpp in Sources */,
 				CDEA763014608A53008B31F1 /* PlatformClockCA.cpp in Sources */,
 				CDEA76341460B56F008B31F1 /* ClockGeneric.cpp in Sources */,
+				CD27F6E51457685A0078207D /* JSMediaController.cpp in Sources */,
+				CD27F6E7145770D30078207D /* MediaController.cpp in Sources */,
+				CDD525D7145B6DD0008D204D /* JSHTMLMediaElementCustom.cpp in Sources */,
 				CDEA76351460B71A008B31F1 /* Clock.cpp in Sources */,
 				976F36EA14686225005E93B4 /* SecurityContext.cpp in Sources */,
 				076970861463AD8700F502CF /* TextTrackList.cpp in Sources */,
diff --git a/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.cpp b/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.cpp
index 1a3537d..0235949 100644
--- a/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.cpp
+++ b/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.cpp
@@ -113,6 +113,11 @@
     return MediaPlayer::isAvailable();
 }
 
+bool RuntimeEnabledFeatures::mediaControllerEnabled()
+{
+    return MediaPlayer::isAvailable();
+}
+
 bool RuntimeEnabledFeatures::mediaErrorEnabled()
 {
     return MediaPlayer::isAvailable();
diff --git a/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.h b/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.h
index 39fc852..a64a283 100644
--- a/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.h
+++ b/Source/WebCore/bindings/generic/RuntimeEnabledFeatures.h
@@ -92,6 +92,7 @@
     static bool htmlAudioElementEnabled();
     static bool htmlVideoElementEnabled();
     static bool htmlSourceElementEnabled();
+    static bool mediaControllerEnabled();
     static bool mediaErrorEnabled();
     static bool timeRangesEnabled();
 #endif
diff --git a/Source/WebCore/bindings/js/JSBindingsAllInOne.cpp b/Source/WebCore/bindings/js/JSBindingsAllInOne.cpp
index 78ce986..52b96f9 100644
--- a/Source/WebCore/bindings/js/JSBindingsAllInOne.cpp
+++ b/Source/WebCore/bindings/js/JSBindingsAllInOne.cpp
@@ -92,6 +92,7 @@
 #include "JSHTMLFrameSetElementCustom.cpp"
 #include "JSHTMLInputElementCustom.cpp"
 #include "JSHTMLLinkElementCustom.cpp"
+#include "JSHTMLMediaElementCustom.cpp"
 #include "JSHTMLObjectElementCustom.cpp"
 #include "JSHTMLOptionsCollectionCustom.cpp"
 #include "JSHTMLOutputElementCustom.cpp"
diff --git a/Source/WebCore/bindings/js/JSHTMLMediaElementCustom.cpp b/Source/WebCore/bindings/js/JSHTMLMediaElementCustom.cpp
new file mode 100644
index 0000000..4430065
--- /dev/null
+++ b/Source/WebCore/bindings/js/JSHTMLMediaElementCustom.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 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(VIDEO)
+
+#include "JSHTMLMediaElement.h"
+
+#include "JSMediaController.h"
+
+namespace WebCore {
+
+using namespace JSC;
+
+void JSHTMLMediaElement::setController(ExecState*, JSValue value)
+{
+    HTMLMediaElement* imp = static_cast<HTMLMediaElement*>(impl());
+    // 4.8.10.11.2 Media controllers: controller attribute.
+    // On setting, it must first remove the element's mediagroup attribute, if any, 
+    imp->setMediaGroup(String());
+    // and then set the current media controller to the given value.
+    imp->setController(toMediaController(value));
+}
+
+}
+#endif
diff --git a/Source/WebCore/bindings/v8/custom/V8HTMLMediaElementCustom.cpp b/Source/WebCore/bindings/v8/custom/V8HTMLMediaElementCustom.cpp
new file mode 100644
index 0000000..e92504c
--- /dev/null
+++ b/Source/WebCore/bindings/v8/custom/V8HTMLMediaElementCustom.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 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(VIDEO)
+
+#include "V8HTMLMediaElement.h"
+
+#include "V8MediaController.h"
+#include "V8Proxy.h"
+
+namespace WebCore {
+
+void V8HTMLMediaElement::controllerAccessorSetter(v8::Local<v8::String> name, v8::Local<v8::Value> value, const v8::AccessorInfo& info)
+{
+    INC_STATS("DOM.HTMLMediaElement.mediaController._set");
+    HTMLMediaElement* imp = V8HTMLMediaElement::toNative(info.Holder());
+    MediaController* controller = 0;
+    if (V8MediaController::HasInstance(value))
+        controller = V8MediaController::toNative(value->ToObject());
+    
+    if (!controller) {
+        throwError("Value is not of type MediaController");
+        return;
+    }
+
+    // 4.8.10.11.2 Media controllers: controller attribute.
+    // On setting, it must first remove the element's mediagroup attribute, if any, 
+    imp->setMediaGroup(String());
+    // and then set the current media controller to the given value.
+    imp->setController(controller);
+}
+
+}
+
+#endif
+
diff --git a/Source/WebCore/dom/EventTarget.h b/Source/WebCore/dom/EventTarget.h
index e621b5d..bade444 100644
--- a/Source/WebCore/dom/EventTarget.h
+++ b/Source/WebCore/dom/EventTarget.h
@@ -55,6 +55,7 @@
     class IDBVersionChangeRequest;
     class JavaScriptAudioNode;
     class LocalMediaStream;
+    class MediaController;
     class MediaStream;
     class MessagePort;
     class Node;
diff --git a/Source/WebCore/dom/EventTargetFactory.in b/Source/WebCore/dom/EventTargetFactory.in
index 0ce24f5..78396bd 100644
--- a/Source/WebCore/dom/EventTargetFactory.in
+++ b/Source/WebCore/dom/EventTargetFactory.in
@@ -13,6 +13,7 @@
 IDBVersionChangeRequest conditional=INDEXED_DATABASE
 JavaScriptAudioNode conditional=WEB_AUDIO
 LocalMediaStream conditional=MEDIA_STREAM
+MediaController conditional=VIDEO
 MediaStream conditional=MEDIA_STREAM
 MessagePort
 Node
diff --git a/Source/WebCore/html/HTMLAttributeNames.in b/Source/WebCore/html/HTMLAttributeNames.in
index f1e354b..ae5fb71 100644
--- a/Source/WebCore/html/HTMLAttributeNames.in
+++ b/Source/WebCore/html/HTMLAttributeNames.in
@@ -148,6 +148,7 @@
 maxlength
 mayscript
 media
+mediagroup
 method
 min
 multiple
diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp
index 464af90..1c476ff 100644
--- a/Source/WebCore/html/HTMLMediaElement.cpp
+++ b/Source/WebCore/html/HTMLMediaElement.cpp
@@ -52,6 +52,7 @@
 #include "HTMLSourceElement.h"
 #include "HTMLVideoElement.h"
 #include "Logging.h"
+#include "MediaController.h"
 #include "MediaControls.h"
 #include "MediaDocument.h"
 #include "MediaError.h"
@@ -138,6 +139,31 @@
 #endif
 
 using namespace HTMLNames;
+using namespace std;
+
+typedef HashMap<Document*, HashSet<HTMLMediaElement*> > DocumentElementSetMap;
+static DocumentElementSetMap& documentToElementSetMap()
+{
+    DEFINE_STATIC_LOCAL(DocumentElementSetMap, map, ());
+    return map;
+}
+
+static void addElementToDocumentMap(HTMLMediaElement* element, Document* document)
+{
+    DocumentElementSetMap& map = documentToElementSetMap();
+    HashSet<HTMLMediaElement*> set = map.take(document);
+    set.add(element);
+    map.add(document, set);
+}
+
+static void removeElementFromDocumentMap(HTMLMediaElement* element, Document* document)
+{
+    DocumentElementSetMap& map = documentToElementSetMap();
+    HashSet<HTMLMediaElement*> set = map.take(document);
+    set.remove(element);
+    if (!set.isEmpty())
+        map.add(document, set);
+}
 
 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* document)
     : HTMLElement(tagName, document)
@@ -219,6 +245,7 @@
 #endif
 
     setHasCustomWillOrDidRecalcStyle();
+    addElementToDocumentMap(this, document);
 }
 
 HTMLMediaElement::~HTMLMediaElement()
@@ -238,6 +265,11 @@
             m_textTracks->item(i)->clearClient();
     }
 #endif
+
+    if (m_mediaController)
+        m_mediaController->removeMediaElement(this);
+
+    removeElementFromDocumentMap(this, document());
 }
 
 void HTMLMediaElement::willMoveToNewOwnerDocument()
@@ -247,6 +279,7 @@
     setShouldDelayLoadEvent(false);
     document()->unregisterForDocumentActivationCallbacks(this);
     document()->unregisterForMediaVolumeCallbacks(this);
+    removeElementFromDocumentMap(this, document());
     HTMLElement::willMoveToNewOwnerDocument();
 }
 
@@ -258,6 +291,7 @@
         setShouldDelayLoadEvent(true);
     document()->registerForDocumentActivationCallbacks(this);
     document()->registerForMediaVolumeCallbacks(this);
+    addElementToDocumentMap(this, document());
     HTMLElement::didMoveToNewOwnerDocument();
 }
 
@@ -304,7 +338,9 @@
         if (!autoplay() && m_player)
             m_player->setPreload(m_preload);
 
-    } else if (attrName == onabortAttr)
+    } else if (attrName == mediagroupAttr)
+        setMediaGroup(attr->value());
+    else if (attrName == onabortAttr)
         setAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(this, attr));
     else if (attrName == onbeforeloadAttr)
         setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr));
@@ -613,6 +649,7 @@
         m_seeking = false;
         invalidateCachedTime();
         scheduleEvent(eventNames().emptiedEvent);
+        updateMediaController();
     }
 
     // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
@@ -1248,6 +1285,7 @@
     }
 
     updatePlayState();
+    updateMediaController();
 }
 
 #if ENABLE(MEDIA_SOURCE)
@@ -1547,6 +1585,10 @@
 
 void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec)
 {
+    if (m_mediaController) {
+        ec = INVALID_STATE_ERR;
+        return;
+    }
     seek(time, ec);
 }
 
@@ -1598,16 +1640,24 @@
 void HTMLMediaElement::setPlaybackRate(float rate)
 {
     LOG(Media, "HTMLMediaElement::setPlaybackRate(%f)", rate);
-
+    
     if (m_playbackRate != rate) {
         m_playbackRate = rate;
         invalidateCachedTime();
         scheduleEvent(eventNames().ratechangeEvent);
     }
-    if (m_player && potentiallyPlaying() && m_player->rate() != rate)
+
+    if (m_player && potentiallyPlaying() && m_player->rate() != rate && !m_mediaController)
         m_player->setRate(rate);
 }
 
+void HTMLMediaElement::updatePlaybackRate()
+{
+    float effectiveRate = m_mediaController ? m_mediaController->playbackRate() : m_playbackRate;
+    if (m_player && potentiallyPlaying() && m_player->rate() != effectiveRate && !m_mediaController)
+        m_player->setRate(effectiveRate);
+}
+
 bool HTMLMediaElement::webkitPreservesPitch() const
 {
     return m_webkitPreservesPitch;
@@ -1700,7 +1750,10 @@
         ExceptionCode unused;
         seek(0, unused);
     }
-    
+
+    if (m_mediaController)
+        m_mediaController->bringElementUpToSpeed(this);
+
     if (m_paused) {
         m_paused = false;
         invalidateCachedTime();
@@ -1714,6 +1767,7 @@
     m_autoplaying = false;
 
     updatePlayState();
+    updateMediaController();
 }
 
 void HTMLMediaElement::pause()
@@ -1905,7 +1959,7 @@
     // We can safely call the internal play/pause methods, which don't check restrictions, because
     // this method is only called from the built-in media controller
     if (canPlay()) {
-        setPlaybackRate(defaultPlaybackRate());
+        updatePlaybackRate();
         playInternal();
     } else 
         pauseInternal();
@@ -2333,20 +2387,33 @@
 
     float now = currentTime();
     float dur = duration();
-    if (!isnan(dur) && dur && now >= dur) {
-        if (loop()) {
+    
+    // When the current playback position reaches the end of the media resource when the direction of
+    // playback is forwards, then the user agent must follow these steps:
+    if (!isnan(dur) && dur && now >= dur && m_playbackRate > 0) {
+        // If the media element has a loop attribute specified and does not have a current media controller,
+        if (loop() && !m_mediaController) {
             ExceptionCode ignoredException;
             m_sentEndEvent = false;
-            seek(0, ignoredException);
+            //  then seek to the earliest possible position of the media resource and abort these steps.
+            seek(startTime(), ignoredException);
         } else {
-            if (!m_paused) {
+            // If the media element does not have a current media controller, and the media element
+            // has still ended playback, and the direction of playback is still forwards, and paused
+            // is false,
+            if (!m_mediaController && !m_paused) {
+                // changes paused to true and fires a simple event named pause at the media element.
                 m_paused = true;
                 scheduleEvent(eventNames().pauseEvent);
             }
+            // Queue a task to fire a simple event named ended at the media element.
             if (!m_sentEndEvent) {
                 m_sentEndEvent = true;
                 scheduleEvent(eventNames().endedEvent);
             }
+            // If the media element has a current media controller, then report the controller state
+            // for the media element's current media controller.
+            updateMediaController();
         }
     }
     else
@@ -2533,10 +2600,7 @@
 
 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
 {
-    // FIXME real ranges support
-    if (!maxTimeSeekable())
-        return TimeRanges::create();
-    return TimeRanges::create(minTimeSeekable(), maxTimeSeekable());
+    return m_player ? m_player->seekable() : TimeRanges::create();
 }
 
 bool HTMLMediaElement::potentiallyPlaying() const
@@ -2545,7 +2609,7 @@
     // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the
     // checks in couldPlayIfEnoughData().
     bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA;
-    return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData();
+    return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData() && !isBlockedOnMediaController();
 }
 
 bool HTMLMediaElement::couldPlayIfEnoughData() const
@@ -2567,10 +2631,11 @@
         return false;
 
     // and the current playback position is the end of the media resource and the direction
-    // of playback is forwards and the media element does not have a loop attribute specified,
+    // of playback is forwards, Either the media element does not have a loop attribute specified,
+    // or the media element has a current media controller.
     float now = currentTime();
     if (m_playbackRate > 0)
-        return dur > 0 && now >= dur && !loop();
+        return dur > 0 && now >= dur && (!loop() || m_mediaController);
 
     // or the current playback position is the earliest possible position and the direction 
     // of playback is backwards
@@ -2616,8 +2681,14 @@
     if (!processingMediaPlayerCallback()) {
         Page* page = document()->page();
         float volumeMultiplier = page ? page->mediaVolume() : 1;
-    
-        m_player->setMuted(m_muted);
+        bool shouldMute = m_muted;
+
+        if (m_mediaController) {
+            volumeMultiplier *= m_mediaController->volume();
+            shouldMute = m_mediaController->muted();
+        }
+
+        m_player->setMuted(shouldMute);
         m_player->setVolume(m_volume * volumeMultiplier);
     }
 
@@ -2684,11 +2755,13 @@
         if (hasMediaControls())
             mediaControls()->playbackStopped();
     }
-    
+
+    updateMediaController();
+
     if (renderer())
         renderer()->updateFromElement();
 }
-    
+
 void HTMLMediaElement::setPausedInternal(bool b)
 {
     m_pausedInternal = b;
@@ -2748,6 +2821,7 @@
 
     // Reset m_readyState since m_player is gone.
     m_readyState = HAVE_NOTHING;
+    updateMediaController();
 }
 
 bool HTMLMediaElement::canSuspend() const
@@ -3240,6 +3314,100 @@
 }
 #endif
 
+const String& HTMLMediaElement::mediaGroup() const
+{
+    return m_mediaGroup;
+}
+
+void HTMLMediaElement::setMediaGroup(const String& group)
+{
+    if (m_mediaGroup == group)
+        return;
+    m_mediaGroup = group;
+
+    // When a media element is created with a mediagroup attribute, and when a media element's mediagroup 
+    // attribute is set, changed, or removed, the user agent must run the following steps:
+    // 1. Let m [this] be the media element in question.
+    // 2. Let m have no current media controller, if it currently has one.
+    setController(0);
+
+    // 3. If m's mediagroup attribute is being removed, then abort these steps.
+    if (group.isNull() || group.isEmpty())
+        return;
+
+    // 4. If there is another media element whose Document is the same as m's Document (even if one or both
+    // of these elements are not actually in the Document), 
+    HashSet<HTMLMediaElement*> elements = documentToElementSetMap().get(document());
+    for (HashSet<HTMLMediaElement*>::iterator i = elements.begin(); i != elements.end(); ++i) {
+        if (*i == this)
+            continue;
+
+        // and which also has a mediagroup attribute, and whose mediagroup attribute has the same value as
+        // the new value of m's mediagroup attribute,        
+        if ((*i)->mediaGroup() == group) {
+            //  then let controller be that media element's current media controller.
+            setController((*i)->controller());
+            return;
+        }
+    }
+
+    // Otherwise, let controller be a newly created MediaController.
+    setController(MediaController::create(Node::scriptExecutionContext()));
+}
+
+MediaController* HTMLMediaElement::controller() const
+{
+    return m_mediaController.get();
+}
+
+void HTMLMediaElement::setController(PassRefPtr<MediaController> controller)
+{
+    if (m_mediaController)
+        m_mediaController->removeMediaElement(this);
+
+    m_mediaController = controller;
+
+    if (m_mediaController)
+        m_mediaController->addMediaElement(this);
+}
+
+void HTMLMediaElement::updateMediaController()
+{
+    if (m_mediaController)
+        m_mediaController->reportControllerState();
+}
+
+bool HTMLMediaElement::isBlocked() const
+{
+    // A media element is a blocked media element if its readyState attribute is in the
+    // HAVE_NOTHING state, the HAVE_METADATA state, or the HAVE_CURRENT_DATA state,
+    if (m_readyState <= HAVE_CURRENT_DATA)
+        return true;
+
+    // or if the element has paused for user interaction.
+    return pausedForUserInteraction();
+}
+
+bool HTMLMediaElement::isBlockedOnMediaController() const
+{
+    if (!m_mediaController)
+        return false;
+
+    // A media element is blocked on its media controller if the MediaController is a blocked 
+    // media controller,
+    if (m_mediaController->isBlocked())
+        return true;
+
+    // or if its media controller position is either before the media resource's earliest possible 
+    // position relative to the MediaController's timeline or after the end of the media resource 
+    // relative to the MediaController's timeline.
+    float mediaControllerPosition = m_mediaController->currentTime();
+    if (mediaControllerPosition < startTime() || mediaControllerPosition > startTime() + duration())
+        return true;
+
+    return false;
+}
+
 }
 
 #endif
diff --git a/Source/WebCore/html/HTMLMediaElement.h b/Source/WebCore/html/HTMLMediaElement.h
index fb3686b..bad1d86 100644
--- a/Source/WebCore/html/HTMLMediaElement.h
+++ b/Source/WebCore/html/HTMLMediaElement.h
@@ -31,6 +31,7 @@
 #include "HTMLElement.h"
 #include "ActiveDOMObject.h"
 #include "MediaCanStartListener.h"
+#include "MediaControllerInterface.h"
 #include "MediaPlayer.h"
 
 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
@@ -52,6 +53,7 @@
 class Event;
 class HTMLSourceElement;
 class HTMLTrackElement;
+class MediaController;
 class MediaControls;
 class MediaError;
 class KURL;
@@ -66,7 +68,7 @@
 // But it can't be until the Chromium WebMediaPlayerClientImpl class is fixed so it
 // no longer depends on typecasting a MediaPlayerClient to an HTMLMediaElement.
 
-class HTMLMediaElement : public HTMLElement, public MediaPlayerClient, private MediaCanStartListener, public ActiveDOMObject
+class HTMLMediaElement : public HTMLElement, public MediaPlayerClient, private MediaCanStartListener, public ActiveDOMObject, public MediaControllerInterface
 #if ENABLE(VIDEO_TRACK)
     , private TextTrackClient
 #endif
@@ -112,7 +114,7 @@
 
     enum NetworkState { NETWORK_EMPTY, NETWORK_IDLE, NETWORK_LOADING, NETWORK_NO_SOURCE };
     NetworkState networkState() const;
-    
+
     String preload() const;    
     void setPreload(const String&);
 
@@ -121,7 +123,6 @@
     String canPlayType(const String& mimeType) const;
 
 // ready state
-    enum ReadyState { HAVE_NOTHING, HAVE_METADATA, HAVE_CURRENT_DATA, HAVE_FUTURE_DATA, HAVE_ENOUGH_DATA };
     ReadyState readyState() const;
     bool seeking() const;
 
@@ -136,6 +137,7 @@
     void setDefaultPlaybackRate(float);
     float playbackRate() const;
     void setPlaybackRate(float);
+    void updatePlaybackRate();
     bool webkitPreservesPitch() const;
     void setWebkitPreservesPitch(bool);
     PassRefPtr<TimeRanges> played();
@@ -251,6 +253,12 @@
     enum InvalidURLAction { DoNothing, Complain };
     bool isSafeToLoadURL(const KURL&, InvalidURLAction);
 
+    const String& mediaGroup() const;
+    void setMediaGroup(const String&);
+
+    MediaController* controller() const;
+    void setController(PassRefPtr<MediaController>);
+
 protected:
     HTMLMediaElement(const QualifiedName&, Document*);
     virtual ~HTMLMediaElement();
@@ -404,6 +412,8 @@
     // Pauses playback without changing any states or generating events
     void setPausedInternal(bool);
 
+    void setPlaybackRateInternal(float);
+
     virtual void mediaCanStart();
 
     void setShouldDelayLoadEvent(bool);
@@ -422,6 +432,13 @@
     virtual void setItemValueText(const String&, ExceptionCode&);
 #endif
 
+    void updateMediaController();
+    bool isBlocked() const;
+    bool isBlockedOnMediaController() const;
+    bool hasCurrentSrc() const { return !m_currentSrc.isEmpty(); }
+    bool isLiveStream() const { return movieLoadType() == MediaPlayer::LiveStream; }
+    bool isAutoplaying() const { return m_autoplaying; }
+
     Timer<HTMLMediaElement> m_loadTimer;
     Timer<HTMLMediaElement> m_asyncEventTimer;
     Timer<HTMLMediaElement> m_progressEventTimer;
@@ -533,6 +550,10 @@
     CueIntervalTree m_cueTree;
     Vector<CueIntervalTree::IntervalType> m_currentlyVisibleCues;
 #endif
+
+    String m_mediaGroup;
+    friend class MediaController;
+    RefPtr<MediaController> m_mediaController;
 };
 
 #if ENABLE(VIDEO_TRACK)
diff --git a/Source/WebCore/html/HTMLMediaElement.idl b/Source/WebCore/html/HTMLMediaElement.idl
index 4a6fe54..23b8868 100644
--- a/Source/WebCore/html/HTMLMediaElement.idl
+++ b/Source/WebCore/html/HTMLMediaElement.idl
@@ -115,5 +115,8 @@
     [EnabledAtRuntime=webkitVideoTrack] TextTrack addTrack(in DOMString kind, in [Optional] DOMString label, in [Optional] DOMString language);
     readonly attribute [EnabledAtRuntime=webkitVideoTrack] TextTrackList textTracks;
 #endif
+
+    attribute [Reflect, ConvertNullToNullString, ConvertNullStringToNull] DOMString mediaGroup;
+    attribute [CustomSetter] MediaController controller;
 };
 }
diff --git a/Source/WebCore/html/MediaController.cpp b/Source/WebCore/html/MediaController.cpp
new file mode 100644
index 0000000..5903f56
--- /dev/null
+++ b/Source/WebCore/html/MediaController.cpp
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2011 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 COMPUTER, 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 COMPUTER, 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(VIDEO)
+#include "MediaController.h"
+
+#include "Clock.h"
+#include "ExceptionCode.h"
+#include "HTMLMediaElement.h"
+#include "TimeRanges.h"
+#include <wtf/StdLibExtras.h>
+#include <wtf/text/AtomicString.h>
+
+using namespace WebCore;
+using namespace std;
+
+PassRefPtr<MediaController> MediaController::create(ScriptExecutionContext* context)
+{
+    return adoptRef(new MediaController(context));
+}
+
+MediaController::MediaController(ScriptExecutionContext* context)
+    : m_paused(false)
+    , m_defaultPlaybackRate(1)
+    , m_volume(1)
+    , m_muted(false)
+    , m_readyState(HAVE_NOTHING)
+    , m_playbackState(WAITING)
+    , m_asyncEventTimer(this, &MediaController::asyncEventTimerFired)
+    , m_closedCaptionsVisible(false)
+    , m_clock(Clock::create())
+    , m_scriptExecutionContext(context)
+{
+}
+
+MediaController::~MediaController()
+{
+}
+
+void MediaController::addMediaElement(HTMLMediaElement* element)
+{
+    ASSERT(element);
+    ASSERT(!m_mediaElements.contains(element));
+
+    m_mediaElements.append(element);
+    bringElementUpToSpeed(element);
+}
+
+void MediaController::removeMediaElement(HTMLMediaElement* element)
+{
+    ASSERT(element);
+    ASSERT(m_mediaElements.contains(element));
+    m_mediaElements.remove(m_mediaElements.find(element));
+}
+
+bool MediaController::containsMediaElement(HTMLMediaElement* element) const
+{
+    return m_mediaElements.contains(element);
+}
+
+PassRefPtr<TimeRanges> MediaController::buffered() const
+{
+    if (m_mediaElements.isEmpty())
+        return TimeRanges::create();
+
+    // The buffered attribute must return a new static normalized TimeRanges object that represents 
+    // the intersection of the ranges of the media resources of the slaved media elements that the 
+    // user agent has buffered, at the time the attribute is evaluated.
+    RefPtr<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered();
+    for (size_t index = 1; index < m_mediaElements.size(); ++index)
+        bufferedRanges->intersectWith(m_mediaElements[index]->buffered().get());
+    return bufferedRanges;
+}
+
+PassRefPtr<TimeRanges> MediaController::seekable() const
+{
+    if (m_mediaElements.isEmpty())
+        return TimeRanges::create();
+
+    // The seekable attribute must return a new static normalized TimeRanges object that represents
+    // the intersection of the ranges of the media resources of the slaved media elements that the
+    // user agent is able to seek to, at the time the attribute is evaluated.
+    RefPtr<TimeRanges> seekableRanges = m_mediaElements.first()->seekable();
+    for (size_t index = 1; index < m_mediaElements.size(); ++index)
+        seekableRanges->intersectWith(m_mediaElements[index]->seekable().get());
+    return seekableRanges;
+}
+
+PassRefPtr<TimeRanges> MediaController::played()
+{
+    if (m_mediaElements.isEmpty())
+        return TimeRanges::create();
+
+    // The played attribute must return a new static normalized TimeRanges object that represents 
+    // the union of the ranges of the media resources of the slaved media elements that the 
+    // user agent has so far rendered, at the time the attribute is evaluated.
+    RefPtr<TimeRanges> playedRanges = m_mediaElements.first()->played();
+    for (size_t index = 1; index < m_mediaElements.size(); ++index)
+        playedRanges->unionWith(m_mediaElements[index]->played().get());
+    return playedRanges;
+}
+
+float MediaController::duration() const
+{
+    // FIXME: Investigate caching the maximum duration and only updating the cached value
+    // when the slaved media elements' durations change.
+    float maxDuration = 0;
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        float duration = m_mediaElements[index]->duration();
+        if (isnan(duration))
+            continue;
+        maxDuration = max(maxDuration, duration);
+    }
+    return maxDuration;
+}
+
+float MediaController::currentTime() const
+{
+    if (m_mediaElements.isEmpty())
+        return 0;
+    
+    return m_clock->currentTime();
+}
+
+void MediaController::setCurrentTime(float time, ExceptionCode& code)
+{
+    // When the user agent is to seek the media controller to a particular new playback position, 
+    // it must follow these steps:
+    // If the new playback position is less than zero, then set it to zero.
+    time = max(0.0f, time);
+    
+    // If the new playback position is greater than the media controller duration, then set it 
+    // to the media controller duration.
+    time = min(time, duration());
+    
+    // Set the media controller position to the new playback position.
+    m_clock->setCurrentTime(time);
+    
+    // Seek each slaved media element to the new playback position relative to the media element timeline.
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->seek(time, code);
+}
+
+void MediaController::play()
+{
+    // When the play() method is invoked, if the MediaController is a paused media controller,
+    if (!m_paused)
+        return;
+
+    // the user agent must change the MediaController into a playing media controller,
+    m_paused = false;
+    // queue a task to fire a simple event named play at the MediaController,
+    scheduleEvent(eventNames().playEvent);
+    // and then report the controller state of the MediaController.
+    reportControllerState();
+}
+
+void MediaController::pause()
+{
+    // When the pause() method is invoked, if the MediaController is a playing media controller,
+    if (m_paused)
+        return;
+
+    // then the user agent must change the MediaController into a paused media controller,
+    m_paused = true;
+    // queue a task to fire a simple event named pause at the MediaController,
+    scheduleEvent(eventNames().pauseEvent);
+    // and then report the controller state of the MediaController.
+    reportControllerState();
+}
+
+void MediaController::setDefaultPlaybackRate(float rate)
+{
+    if (m_defaultPlaybackRate == rate)
+        return;
+
+    // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
+    // default playback rate to the new value,
+    m_defaultPlaybackRate = rate;
+
+    // then queue a task to fire a simple event named ratechange at the MediaController.
+    scheduleEvent(eventNames().ratechangeEvent);
+}
+
+float MediaController::playbackRate() const
+{
+    return m_clock->playRate();
+}
+
+void MediaController::setPlaybackRate(float rate)
+{
+    if (m_clock->playRate() == rate)
+        return;
+
+    // The playbackRate attribute, on setting, must set the MediaController's media controller 
+    // playback rate to the new value,
+    m_clock->setPlayRate(rate);
+
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->updatePlaybackRate();
+
+    // then queue a task to fire a simple event named ratechange at the MediaController.
+    scheduleEvent(eventNames().ratechangeEvent);
+}
+
+void MediaController::setVolume(float level, ExceptionCode& code)
+{
+    if (m_volume == level)
+        return;
+
+    // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an 
+    // IndexSizeError exception must be raised instead.
+    if (level < 0 || level > 1) {
+        code = INDEX_SIZE_ERR;
+        return;
+    }
+        
+    // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
+    // must set the MediaController's media controller volume multiplier to the new value
+    m_volume = level;
+
+    // and queue a task to fire a simple event named volumechange at the MediaController.
+    scheduleEvent(eventNames().volumechangeEvent);
+
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->updateVolume();
+}
+
+void MediaController::setMuted(bool flag)
+{
+    if (m_muted == flag)
+        return;
+
+    // The muted attribute, on setting, must set the MediaController's media controller mute override
+    // to the new value
+    m_muted = flag;
+
+    // and queue a task to fire a simple event named volumechange at the MediaController.
+    scheduleEvent(eventNames().volumechangeEvent);
+
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->updateVolume();
+}
+
+void MediaController::reportControllerState()
+{
+    updateReadyState();
+    updatePlaybackState();
+}
+
+static AtomicString eventNameForReadyState(MediaControllerInterface::ReadyState state)
+{
+    switch (state) {
+    case MediaControllerInterface::HAVE_NOTHING:
+        return eventNames().emptiedEvent;
+    case MediaControllerInterface::HAVE_METADATA:
+        return eventNames().loadedmetadataEvent;
+    case MediaControllerInterface::HAVE_CURRENT_DATA:
+        return eventNames().loadeddataEvent;
+    case MediaControllerInterface::HAVE_FUTURE_DATA:
+        return eventNames().canplayEvent;
+    case MediaControllerInterface::HAVE_ENOUGH_DATA:
+        return eventNames().canplaythroughEvent;
+    default:
+        ASSERT_NOT_REACHED();
+        return nullAtom;
+    }
+}
+
+void MediaController::updateReadyState()
+{
+    ReadyState oldReadyState = m_readyState;
+    ReadyState newReadyState;
+    
+    if (m_mediaElements.isEmpty()) {
+        // If the MediaController has no slaved media elements, let new readiness state be 0.
+        newReadyState = HAVE_NOTHING;
+    } else {
+        // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
+        // slaved media elements.
+        newReadyState = m_mediaElements.first()->readyState();
+        for (size_t index = 1; index < m_mediaElements.size(); ++index)
+            newReadyState = min(newReadyState, m_mediaElements[index]->readyState());
+    }
+
+    if (newReadyState == oldReadyState) 
+        return;
+
+    // If the MediaController's most recently reported readiness state is greater than new readiness 
+    // state then queue a task to fire a simple event at the MediaController object, whose name is the
+    // event name corresponding to the value of new readiness state given in the table below. [omitted]
+    if (oldReadyState > newReadyState) {
+        scheduleEvent(eventNameForReadyState(newReadyState));
+        return;
+    }
+
+    // If the MediaController's most recently reported readiness state is less than the new readiness
+    // state, then run these substeps:
+    // 1. Let next state be the MediaController's most recently reported readiness state.
+    ReadyState nextState = oldReadyState;
+    do {
+        // 2. Loop: Increment next state by one.
+        nextState = static_cast<ReadyState>(nextState + 1);
+        // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
+        // event name corresponding to the value of next state given in the table below. [omitted]
+        scheduleEvent(eventNameForReadyState(nextState));        
+        // If next state is less than new readiness state, then return to the step labeled loop
+    } while (nextState < newReadyState);
+
+    // Let the MediaController's most recently reported readiness state be new readiness state.
+    m_readyState = newReadyState;
+}
+
+void MediaController::updatePlaybackState()
+{
+    PlaybackState oldPlaybackState = m_playbackState;
+    PlaybackState newPlaybackState;
+
+    // Initialize new playback state by setting it to the state given for the first matching 
+    // condition from the following list:
+    if (m_mediaElements.isEmpty()) {
+        // If the MediaController has no slaved media elements
+        // Let new playback state be waiting.
+        newPlaybackState = WAITING;
+    } else if (hasEnded()) {
+        // If all of the MediaController's slaved media elements have ended playback and the media
+        // controller playback rate is positive or zero
+        // Let new playback state be ended.
+        newPlaybackState = ENDED;
+    } else if (isBlocked()) {
+        // If the MediaController is a blocked media controller
+        // Let new playback state be waiting.
+        newPlaybackState = WAITING;
+    } else {
+        // Otherwise
+        // Let new playback state be playing.
+        newPlaybackState = PLAYING;
+    }
+
+    // If the MediaController's most recently reported playback state is not equal to new playback state
+    if (newPlaybackState == oldPlaybackState)
+        return;
+
+    // and the new playback state is ended,
+    if (newPlaybackState == ENDED) {
+        // then queue a task that, if the MediaController object is a playing media controller, and 
+        // all of the MediaController's slaved media elements have still ended playback, and the 
+        // media controller playback rate is still positive or zero, 
+        if (!m_paused && hasEnded()) {
+            // changes the MediaController object to a paused media controller
+            m_paused = true;
+            m_clock->stop();
+
+            // and then fires a simple event named pause at the MediaController object.
+            scheduleEvent(eventNames().pauseEvent);
+        }
+    }
+
+    // If the MediaController's most recently reported playback state is not equal to new playback state
+    // then queue a task to fire a simple event at the MediaController object, whose name is playing 
+    // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
+    AtomicString eventName;
+    switch (newPlaybackState) {
+    case WAITING:
+        eventName = eventNames().waitingEvent;
+        break;
+    case ENDED:
+        eventName = eventNames().endedEvent;
+        break;
+    case PLAYING:
+        eventName = eventNames().playingEvent;
+        m_clock->start();
+        break;
+    default:
+        ASSERT_NOT_REACHED();
+    }
+    scheduleEvent(eventName);
+
+    // Let the MediaController's most recently reported playback state be new playback state.
+    m_playbackState = newPlaybackState;
+
+    updateMediaElements();
+}
+
+void MediaController::updateMediaElements()
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->updatePlayState();
+}
+
+void MediaController::bringElementUpToSpeed(HTMLMediaElement* element)
+{
+    ASSERT(element);
+    ASSERT(m_mediaElements.contains(element));
+
+    // When the user agent is to bring a media element up to speed with its new media controller,
+    // it must seek that media element to the MediaController's media controller position relative
+    // to the media element's timeline.
+    ExceptionCode ignoredCode = 0;
+    element->seek(currentTime(), ignoredCode);
+}
+
+bool MediaController::isBlocked() const
+{
+    // A MediaController is a blocked media controller if the MediaController is a paused media 
+    // controller,
+    if (m_paused)
+        return true;
+    
+    if (m_mediaElements.isEmpty())
+        return false;
+    
+    bool allPaused = true;
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        HTMLMediaElement* element = m_mediaElements[index];
+        //  or if any of its slaved media elements are blocked media elements,
+        if (element->isBlocked())
+            return true;
+        
+        // or if any of its slaved media elements whose autoplaying flag is true still have their 
+        // paused attribute set to true,
+        if (element->isAutoplaying() && element->paused())
+            return true;
+        
+        if (!element->paused())
+            allPaused = false;
+    }
+    
+    // or if all of its slaved media elements have their paused attribute set to true.
+    return allPaused;
+}
+
+bool MediaController::hasEnded() const
+{
+    // If the ... media controller playback rate is positive or zero
+    if (m_clock->playRate() < 0)
+        return false;
+
+    // [and] all of the MediaController's slaved media elements have ended playback ... let new
+    // playback state be ended.
+    if (m_mediaElements.isEmpty())
+        return false;
+    
+    bool allHaveEnded = true;
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (!m_mediaElements[index]->ended())
+            allHaveEnded = false;
+    }
+    return allHaveEnded;
+}
+
+void MediaController::scheduleEvent(const AtomicString& eventName)
+{
+    m_pendingEvents.append(Event::create(eventName, false, true));
+    if (!m_asyncEventTimer.isActive())
+        m_asyncEventTimer.startOneShot(0);
+}
+
+void MediaController::asyncEventTimerFired(Timer<MediaController>*)
+{
+    Vector<RefPtr<Event> > pendingEvents;
+    ExceptionCode ec = 0;
+    
+    m_pendingEvents.swap(pendingEvents);
+    size_t count = pendingEvents.size();
+    for (size_t index = 0; index < count; ++index)
+        dispatchEvent(pendingEvents[index].release(), ec);
+}
+
+bool MediaController::hasAudio() const
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (m_mediaElements[index]->hasAudio())
+            return true;
+    }
+    return false;
+}
+
+bool MediaController::hasVideo() const
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (m_mediaElements[index]->hasVideo())
+            return true;
+    }
+    return false;
+}
+
+bool MediaController::hasClosedCaptions() const
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (m_mediaElements[index]->hasClosedCaptions())
+            return true;
+    }
+    return false;
+}
+
+void MediaController::setClosedCaptionsVisible(bool visible)
+{
+    m_closedCaptionsVisible = visible;
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->setClosedCaptionsVisible(visible);
+}
+
+bool MediaController::supportsScanning() const
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (!m_mediaElements[index]->supportsScanning())
+            return false;
+    }
+    return true;
+}
+
+void MediaController::beginScrubbing()
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->beginScrubbing();
+}
+
+void MediaController::endScrubbing()
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->endScrubbing();
+}
+
+bool MediaController::canPlay() const
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (!m_mediaElements[index]->canPlay())
+            return false;
+    }
+    return true;
+}
+
+bool MediaController::isLiveStream() const
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (!m_mediaElements[index]->isLiveStream())
+            return false;
+    }
+    return true;
+}
+
+bool MediaController::hasCurrentSrc() const
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index) {
+        if (!m_mediaElements[index]->hasCurrentSrc())
+            return false;
+    }
+    return true;
+}
+
+void MediaController::returnToRealtime()
+{
+    for (size_t index = 0; index < m_mediaElements.size(); ++index)
+        m_mediaElements[index]->returnToRealtime();
+}
+
+const AtomicString& MediaController::interfaceName() const
+{
+    return eventNames().interfaceForMediaController;
+}
+
+#endif
diff --git a/Source/WebCore/html/MediaController.h b/Source/WebCore/html/MediaController.h
new file mode 100644
index 0000000..686cd09
--- /dev/null
+++ b/Source/WebCore/html/MediaController.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 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 COMPUTER, 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 COMPUTER, 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. 
+ */
+
+#ifndef MediaController_h
+#define MediaController_h
+
+#if ENABLE(VIDEO)
+
+#include "ActiveDOMObject.h"
+#include "Event.h"
+#include "EventListener.h"
+#include "EventTarget.h"
+#include "MediaControllerInterface.h"
+#include "Timer.h"
+#include <wtf/PassRefPtr.h>
+#include <wtf/RefCounted.h>
+#include <wtf/Vector.h>
+
+namespace WebCore {
+
+class Clock;
+class HTMLMediaElement;
+class Event;
+class ScriptExecutionContext;
+
+class MediaController : public RefCounted<MediaController>, public MediaControllerInterface, public EventTarget {
+public:
+    static PassRefPtr<MediaController> create(ScriptExecutionContext*);
+    virtual ~MediaController();
+
+    void addMediaElement(HTMLMediaElement*);
+    void removeMediaElement(HTMLMediaElement*);
+    bool containsMediaElement(HTMLMediaElement*) const;
+
+    const String& mediaGroup() const { return m_mediaGroup; }
+    
+    virtual PassRefPtr<TimeRanges> buffered() const;
+    virtual PassRefPtr<TimeRanges> seekable() const;
+    virtual PassRefPtr<TimeRanges> played();
+    
+    virtual float duration() const;
+    virtual float currentTime() const;
+    virtual void setCurrentTime(float, ExceptionCode&);
+    
+    virtual bool paused() const { return m_paused; }
+    virtual void play();
+    virtual void pause();
+    
+    virtual float defaultPlaybackRate() const { return m_defaultPlaybackRate; }
+    virtual void setDefaultPlaybackRate(float);
+    
+    virtual float playbackRate() const;
+    virtual void setPlaybackRate(float);
+    
+    virtual float volume() const { return m_volume; }
+    virtual void setVolume(float, ExceptionCode&);
+    
+    virtual bool muted() const { return m_muted; }
+    virtual void setMuted(bool);
+    
+    virtual ReadyState readyState() const { return m_readyState; }
+
+    enum PlaybackState { WAITING, PLAYING, ENDED };
+    virtual PlaybackState playbackState() const { return m_playbackState; }
+
+    virtual bool supportsFullscreen() const { return false; }
+    virtual bool isFullscreen() const { return false; }
+    virtual void enterFullscreen() { }
+
+    virtual bool hasAudio() const;
+    virtual bool hasVideo() const;
+    virtual bool hasClosedCaptions() const;
+    virtual void setClosedCaptionsVisible(bool);
+    virtual bool closedCaptionsVisible() const { return m_closedCaptionsVisible; }
+    
+    virtual bool supportsScanning() const;
+    
+    virtual void beginScrubbing();
+    virtual void endScrubbing();
+    
+    virtual bool canPlay() const;
+    
+    virtual bool isLiveStream() const;
+    
+    virtual bool hasCurrentSrc() const;
+    
+    virtual void returnToRealtime();
+
+    bool isBlocked() const;
+
+    // EventTarget
+    using RefCounted<MediaController>::ref;
+    using RefCounted<MediaController>::deref;
+
+private:
+    MediaController(ScriptExecutionContext*);
+    void reportControllerState();
+    void updateReadyState();
+    void updatePlaybackState();
+    void updateMediaElements();
+    void bringElementUpToSpeed(HTMLMediaElement*);
+    void scheduleEvent(const AtomicString& eventName);
+    void asyncEventTimerFired(Timer<MediaController>*);
+    bool hasEnded() const;
+
+    // EventTarget
+    virtual void refEventTarget() { ref(); }
+    virtual void derefEventTarget() { deref(); }
+    virtual const AtomicString& interfaceName() const;
+    virtual ScriptExecutionContext* scriptExecutionContext() const { return m_scriptExecutionContext; };
+    virtual EventTargetData* eventTargetData() { return &m_eventTargetData; }
+    virtual EventTargetData* ensureEventTargetData() { return &m_eventTargetData; }
+    EventTargetData m_eventTargetData;
+
+    friend class HTMLMediaElement;
+    friend class MediaControllerEventListener;
+    Vector<HTMLMediaElement*> m_mediaElements;
+    bool m_paused;
+    float m_defaultPlaybackRate;
+    float m_volume;
+    bool m_muted;
+    ReadyState m_readyState;
+    PlaybackState m_playbackState;
+    Vector<RefPtr<Event> > m_pendingEvents;
+    Timer<MediaController> m_asyncEventTimer;
+    String m_mediaGroup;
+    bool m_closedCaptionsVisible;
+    PassRefPtr<Clock> m_clock;
+    ScriptExecutionContext* m_scriptExecutionContext;
+};
+
+} // namespace WebCore
+
+#endif
+#endif
diff --git a/Source/WebCore/html/MediaController.idl b/Source/WebCore/html/MediaController.idl
new file mode 100644
index 0000000..f7b9fb2
--- /dev/null
+++ b/Source/WebCore/html/MediaController.idl
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 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 COMPUTER, 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 COMPUTER, 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. 
+ */
+
+module html {
+    interface [
+        Conditional=VIDEO,
+        Constructor,
+        CallWith=ScriptExecutionContext,
+        GenerateToJS,
+        EventTarget
+    ] MediaController {
+        readonly attribute TimeRanges buffered;
+        readonly attribute TimeRanges seekable;
+
+        readonly attribute double duration;
+        attribute double currentTime
+            setter raises (DOMException);
+
+        readonly attribute boolean paused;
+        readonly attribute TimeRanges played;
+        void play();
+        void pause();
+
+        attribute double defaultPlaybackRate;
+        attribute double playbackRate;
+
+        attribute double volume
+            setter raises (DOMException);
+        attribute boolean muted;
+
+        // EventTarget interface
+        void addEventListener(in DOMString type, 
+                              in EventListener listener, 
+                              in [Optional] boolean useCapture);
+        void removeEventListener(in DOMString type, 
+                                 in EventListener listener, 
+                                 in [Optional] boolean useCapture);
+        boolean dispatchEvent(in Event evt)
+            raises(EventException);
+    };
+}
diff --git a/Source/WebCore/html/MediaControllerInterface.h b/Source/WebCore/html/MediaControllerInterface.h
new file mode 100644
index 0000000..7bcf06c
--- /dev/null
+++ b/Source/WebCore/html/MediaControllerInterface.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 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 COMPUTER, 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 COMPUTER, 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. 
+ */
+
+#ifndef MediaControllerInterface_h
+#define MediaControllerInterface_h
+
+#if ENABLE(VIDEO)
+
+#include <wtf/PassRefPtr.h>
+
+namespace WebCore {
+
+class TimeRanges;
+
+typedef int ExceptionCode;
+
+class MediaControllerInterface {
+public:
+    virtual ~MediaControllerInterface() { };
+    
+    // MediaController IDL:
+    virtual PassRefPtr<TimeRanges> buffered() const = 0;
+    virtual PassRefPtr<TimeRanges> seekable() const = 0;
+    virtual PassRefPtr<TimeRanges> played() = 0;
+    
+    virtual float duration() const = 0;
+    virtual float currentTime() const = 0;
+    virtual void setCurrentTime(float, ExceptionCode&) = 0;
+    
+    virtual bool paused() const = 0;
+    virtual void play() = 0;
+    virtual void pause() = 0;
+    
+    virtual float defaultPlaybackRate() const = 0;
+    virtual void setDefaultPlaybackRate(float) = 0;
+    
+    virtual float playbackRate() const = 0;
+    virtual void setPlaybackRate(float) = 0;
+    
+    virtual float volume() const = 0;
+    virtual void setVolume(float, ExceptionCode&) = 0;
+    
+    virtual bool muted() const = 0;
+    virtual void setMuted(bool) = 0;
+    
+    enum ReadyState { HAVE_NOTHING, HAVE_METADATA, HAVE_CURRENT_DATA, HAVE_FUTURE_DATA, HAVE_ENOUGH_DATA };
+    virtual ReadyState readyState() const = 0;
+
+    // MediaControlElements:
+    virtual bool supportsFullscreen() const = 0;
+    virtual bool isFullscreen() const = 0;
+    virtual void enterFullscreen() = 0;
+
+    virtual bool hasAudio() const = 0;
+    virtual bool hasVideo() const = 0;
+    virtual bool hasClosedCaptions() const = 0;
+    virtual void setClosedCaptionsVisible(bool) = 0;
+    virtual bool closedCaptionsVisible() const = 0;
+
+    virtual bool supportsScanning() const = 0;
+
+    virtual void beginScrubbing() = 0;
+    virtual void endScrubbing() = 0;
+
+    virtual bool canPlay() const = 0;
+
+    virtual bool isLiveStream() const = 0;
+
+    virtual bool hasCurrentSrc() const = 0;
+
+    virtual void returnToRealtime() = 0;
+};
+
+}
+
+#endif
+#endif
diff --git a/Source/WebCore/html/TimeRanges.cpp b/Source/WebCore/html/TimeRanges.cpp
index 984b6bc..d68ea03 100644
--- a/Source/WebCore/html/TimeRanges.cpp
+++ b/Source/WebCore/html/TimeRanges.cpp
@@ -31,13 +31,14 @@
 #include <math.h>
 
 using namespace WebCore;
+using namespace std;
 
 TimeRanges::TimeRanges(float start, float end)
 {
     add(start, end);
 }
 
-PassRefPtr<TimeRanges> TimeRanges::copy()
+PassRefPtr<TimeRanges> TimeRanges::copy() const
 {
     RefPtr<TimeRanges> newSession = TimeRanges::create();
     
@@ -48,6 +49,51 @@
     return newSession.release();
 }
 
+void TimeRanges::invert()
+{
+    RefPtr<TimeRanges> inverted = TimeRanges::create();
+    float posInf = std::numeric_limits<float>::infinity();
+    float negInf = -std::numeric_limits<float>::infinity();
+
+    if (!m_ranges.size())
+        inverted->add(negInf, posInf);
+    else {
+        if (float start = m_ranges.first().m_start != negInf)
+            inverted->add(negInf, start);
+
+        for (size_t index = 0; index + 1 < m_ranges.size(); ++index)
+            inverted->add(m_ranges[index].m_end, m_ranges[index + 1].m_start);
+
+        if (float end = m_ranges.last().m_end != posInf)
+            inverted->add(end, posInf);
+    }
+
+    m_ranges.swap(inverted->m_ranges);
+}
+
+void TimeRanges::intersectWith(const TimeRanges* other)
+{
+    ASSERT(other);
+    RefPtr<TimeRanges> inverted = copy();
+    RefPtr<TimeRanges> invertedOther = other->copy();
+    inverted->unionWith(invertedOther.get());
+    inverted->invert();
+
+    m_ranges.swap(inverted->m_ranges);
+}
+
+void TimeRanges::unionWith(const TimeRanges* other)
+{
+    ASSERT(other);
+    RefPtr<TimeRanges> unioned = copy();
+    for (size_t index = 0; index < other->m_ranges.size(); ++index) {
+        const Range& range = other->m_ranges[index];
+        unioned->add(range.m_start, range.m_end);
+    }
+
+    m_ranges.swap(unioned->m_ranges);
+}
+
 float TimeRanges::start(unsigned index, ExceptionCode& ec) const 
 { 
     if (index >= length()) {
diff --git a/Source/WebCore/html/TimeRanges.h b/Source/WebCore/html/TimeRanges.h
index d1d5637..4257690 100644
--- a/Source/WebCore/html/TimeRanges.h
+++ b/Source/WebCore/html/TimeRanges.h
@@ -46,7 +46,10 @@
         return adoptRef(new TimeRanges(start, end));
     }
 
-    PassRefPtr<TimeRanges> copy();
+    PassRefPtr<TimeRanges> copy() const;
+    void invert();
+    void intersectWith(const TimeRanges*);
+    void unionWith(const TimeRanges*);
 
     unsigned length() const { return m_ranges.size(); }
     float start(unsigned index, ExceptionCode&) const;
diff --git a/Source/WebCore/page/DOMWindow.idl b/Source/WebCore/page/DOMWindow.idl
index c82ab66..38ca089 100644
--- a/Source/WebCore/page/DOMWindow.idl
+++ b/Source/WebCore/page/DOMWindow.idl
@@ -635,6 +635,7 @@
         attribute [Conditional=VIDEO, EnabledAtRuntime] MediaErrorConstructor MediaError;
         attribute [Conditional=VIDEO, EnabledAtRuntime] TimeRangesConstructor TimeRanges;
         attribute [Conditional=VIDEO, EnabledAtRuntime] HTMLSourceElementConstructor HTMLSourceElement;
+        attribute [Conditional=VIDEO, EnabledAtRuntime] MediaControllerConstructor MediaController;
 
 #if defined(ENABLE_ANIMATION_API) && ENABLE_ANIMATION_API
         attribute WebKitAnimationConstructor WebKitAnimation;
diff --git a/Source/WebCore/platform/ClockGeneric.cpp b/Source/WebCore/platform/ClockGeneric.cpp
index 28960c3..aeaf867 100644
--- a/Source/WebCore/platform/ClockGeneric.cpp
+++ b/Source/WebCore/platform/ClockGeneric.cpp
@@ -36,27 +36,27 @@
     , m_rate(1)
     , m_offset(0)
 {
-    m_startTime = m_lastTime = currentTime();
+    m_startTime = m_lastTime = now();
 }
 
 void ClockGeneric::setCurrentTime(float time)
 {
-    m_startTime = m_lastTime = currentTime();
+    m_startTime = m_lastTime = now();
     m_offset = time;
 }
 
 float ClockGeneric::currentTime() const
 {
     if (m_running)
-        m_lastTime = currentTime();
+        m_lastTime = now();
     float time = (narrowPrecisionToFloat(m_lastTime - m_startTime) * m_rate) + m_offset;
     return time;
 }
 
 void ClockGeneric::setPlayRate(float rate)
 {
-    m_offset = currentTime();
-    m_lastTime = m_startTime = currentTime();
+    m_offset = now();
+    m_lastTime = m_startTime = now();
     m_rate = rate;
 }
 
@@ -65,7 +65,7 @@
     if (m_running)
         return;
 
-    m_lastTime = m_startTime = currentTime();
+    m_lastTime = m_startTime = now();
     m_running = true;
 }
 
@@ -74,7 +74,13 @@
     if (!m_running)
         return;
 
-    m_offset = currentTime();
-    m_lastTime = m_startTime = currentTime();
+    m_offset = now();
+    m_lastTime = m_startTime = now();
     m_running = false;
 }
+
+float ClockGeneric::now() const
+{
+    return WTF::currentTime();
+}
+
diff --git a/Source/WebCore/platform/ClockGeneric.h b/Source/WebCore/platform/ClockGeneric.h
index 72b0cb0..5b75bf6 100644
--- a/Source/WebCore/platform/ClockGeneric.h
+++ b/Source/WebCore/platform/ClockGeneric.h
@@ -45,6 +45,8 @@
     virtual void stop();
     virtual bool isRunning() const { return m_running; }
 
+    float now() const;
+
     bool m_running;
     float m_rate;
     float m_offset;
diff --git a/Source/WebCore/platform/graphics/MediaPlayer.cpp b/Source/WebCore/platform/graphics/MediaPlayer.cpp
index 018c7dd..fc77ff2 100644
--- a/Source/WebCore/platform/graphics/MediaPlayer.cpp
+++ b/Source/WebCore/platform/graphics/MediaPlayer.cpp
@@ -609,6 +609,11 @@
     return m_private->buffered();
 }
 
+PassRefPtr<TimeRanges> MediaPlayer::seekable()
+{
+    return m_private->seekable();
+}
+
 float MediaPlayer::maxTimeSeekable()
 {
     return m_private->maxTimeSeekable();
diff --git a/Source/WebCore/platform/graphics/MediaPlayer.h b/Source/WebCore/platform/graphics/MediaPlayer.h
index c96b025..47739a0 100644
--- a/Source/WebCore/platform/graphics/MediaPlayer.h
+++ b/Source/WebCore/platform/graphics/MediaPlayer.h
@@ -241,6 +241,7 @@
     void setPreservesPitch(bool);
 
     PassRefPtr<TimeRanges> buffered();
+    PassRefPtr<TimeRanges> seekable();
     float maxTimeSeekable();
 
     unsigned bytesLoaded();
diff --git a/Source/WebCore/platform/graphics/MediaPlayerPrivate.h b/Source/WebCore/platform/graphics/MediaPlayerPrivate.h
index f8c70ab..6c527d7 100644
--- a/Source/WebCore/platform/graphics/MediaPlayerPrivate.h
+++ b/Source/WebCore/platform/graphics/MediaPlayerPrivate.h
@@ -29,6 +29,7 @@
 #if ENABLE(VIDEO)
 
 #include "MediaPlayer.h"
+#include "TimeRanges.h"
 #include <wtf/Forward.h>
 
 namespace WebCore {
@@ -91,6 +92,7 @@
     virtual MediaPlayer::NetworkState networkState() const = 0;
     virtual MediaPlayer::ReadyState readyState() const = 0;
 
+    virtual PassRefPtr<TimeRanges> seekable() const { return maxTimeSeekable() ? TimeRanges::create(0, maxTimeSeekable()) : TimeRanges::create(); }
     virtual float maxTimeSeekable() const = 0;
     virtual PassRefPtr<TimeRanges> buffered() const = 0;