AirPlay route availability event not always sent
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 6 Jan 2016 23:37:08 +0000 (23:37 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 6 Jan 2016 23:37:08 +0000 (23:37 +0000)
https://bugs.webkit.org/show_bug.cgi?id=152802

Reviewed by Jer Noble.

Source/WebCore:

Test: media/airplay-target-availability.html

* Modules/mediasession/WebMediaSessionManager.cpp:
(WebCore::mediaProducerStateString): Log the new flags.
(WebCore::WebMediaSessionManager::clientStateDidChange): Schedule a client reconfiguration if
  the 'requires monitoring', 'has listener', or 'has audio or video' flags have changed.
(WebCore::WebMediaSessionManager::configurePlaybackTargetMonitoring): Start monitoring if
  at least one client has a listener and at least one has audio/video.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::mediaState): Set new flags.
* html/HTMLMediaElement.h:

* page/MediaProducer.h: Define new flags. Add new state enum.

* platform/graphics/MediaPlaybackTargetContext.h: Initial state is "Unknown".

* platform/mock/MediaPlaybackTargetMock.h:
* platform/mock/MediaPlaybackTargetPickerMock.cpp:
(WebCore::MediaPlaybackTargetPickerMock::externalOutputDeviceAvailable): Enums not bitfields.
(WebCore::MediaPlaybackTargetPickerMock::startingMonitoringPlaybackTargets): Ditto. Don't make
  device change callback if the device state is "Unknown".
(WebCore::MediaPlaybackTargetPickerMock::setState): Ditto.
* platform/mock/MediaPlaybackTargetPickerMock.h:

* testing/Internals.cpp:
(WebCore::Internals::setMockMediaPlaybackTargetPickerState): Support new state.

LayoutTests:

* media/airplay-target-availability-expected.txt: Added.
* media/airplay-target-availability.html: Added.
* platform/mac/TestExpectations: Skip new test on Yosemite.
* platform/efl/TestExpectations: Skip new test.
* platform/gtk/TestExpectations: Ditto.
* platform/win/TestExpectations: Ditto.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@194672 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
LayoutTests/ChangeLog
LayoutTests/media/airplay-target-availability-expected.txt [new file with mode: 0644]
LayoutTests/media/airplay-target-availability.html [new file with mode: 0644]
LayoutTests/platform/efl/TestExpectations
LayoutTests/platform/gtk/TestExpectations
LayoutTests/platform/mac/TestExpectations
LayoutTests/platform/win/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/mediasession/WebMediaSessionManager.cpp
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.h
Source/WebCore/page/MediaProducer.h
Source/WebCore/platform/graphics/MediaPlaybackTargetContext.h
Source/WebCore/platform/mock/MediaPlaybackTargetMock.h
Source/WebCore/platform/mock/MediaPlaybackTargetPickerMock.cpp
Source/WebCore/platform/mock/MediaPlaybackTargetPickerMock.h
Source/WebCore/testing/Internals.cpp

index 6fd080b..bae4fc1 100644 (file)
@@ -1,3 +1,17 @@
+2016-01-06  Eric Carlson  <eric.carlson@apple.com>
+
+        AirPlay route availability event not always sent
+        https://bugs.webkit.org/show_bug.cgi?id=152802
+
+        Reviewed by Jer Noble.
+
+        * media/airplay-target-availability-expected.txt: Added.
+        * media/airplay-target-availability.html: Added.
+        * platform/mac/TestExpectations: Skip new test on Yosemite.
+        * platform/efl/TestExpectations: Skip new test.
+        * platform/gtk/TestExpectations: Ditto.
+        * platform/win/TestExpectations: Ditto.
+
 2016-01-06  Brady Eidson  <beidson@apple.com>
 
         Modern IDB: storage/indexeddb/odd-strings.html is flaky.
diff --git a/LayoutTests/media/airplay-target-availability-expected.txt b/LayoutTests/media/airplay-target-availability-expected.txt
new file mode 100644 (file)
index 0000000..621cfd4
--- /dev/null
@@ -0,0 +1,13 @@
+
+Test that 'webkitplaybacktargetavailabilitychanged' event is sent when at least one video element has playable media.
+
+
+EVENT: 'webkitplaybacktargetavailabilitychanged', event.availability = 'not-available'
+
+** setting src on video that does not have event listener
+
+EVENT: 'webkitplaybacktargetavailabilitychanged', event.availability = 'available'
+
+END OF TEST
+
diff --git a/LayoutTests/media/airplay-target-availability.html b/LayoutTests/media/airplay-target-availability.html
new file mode 100644 (file)
index 0000000..eab046b
--- /dev/null
@@ -0,0 +1,54 @@
+<html>
+    <head>
+        <script src="media-file.js"></script>
+        <script src="video-test.js"></script>
+        <script>
+            var video1;
+            var video2;
+
+            function start()
+            {
+                video1 = document.getElementsByTagName('video')[0];
+                video2 = document.getElementsByTagName('video')[1];
+
+                if (window.internals)
+                    internals.setMockMediaPlaybackTargetPickerEnabled(true);
+
+                video1.addEventListener('webkitplaybacktargetavailabilitychanged', targetAvailabilityChanged);
+                setTimeout(setSource, 200);
+            }
+            
+            function targetAvailabilityChanged(event)
+            {
+                consoleWrite(`<br>EVENT: '${event.type}', event.availability = '${event.availability}'<br>`);
+
+                if (event.availability != "available")
+                    return;
+
+                if (event.availability == "available" && video2.src == "") {
+                    failTest("Event sent before any video is a candidate for airplay");
+                    return;
+                }
+                
+                endTest();
+            }
+            
+            function setSource()
+            {
+                consoleWrite('** setting src on video that does not have event listener');
+                if (window.internals)
+                    internals.setMockMediaPlaybackTargetPickerState("Sleepy TV", "DeviceAvailable");
+                video2.src = findMediaFile('video', 'content/test');
+            }
+
+        </script>
+    </head>
+
+    <body onload="start()">
+        <video controls></video>
+        <br>
+        <video controls></video>
+        <p>Test that 'webkitplaybacktargetavailabilitychanged' event is sent when at least one 
+        video element has playable media.</p>
+    </body>
+</html>
index 3dcb109..5605625 100644 (file)
@@ -2705,6 +2705,9 @@ css2.1/tables/table-anonymous-objects-206.xht [ Missing ]
 css2.1/tables/table-anonymous-objects-207.xht [ Missing ]
 css2.1/tables/table-anonymous-objects-208.xht [ Missing ]
 
+# WIRELESS_PLAYBACK_TARGET not enabled.
+media/airplay-target-availability.html
+
 webkit.org/b/151943 imported/blink/editing/execCommand/4128080-2.html [ Failure ]
 webkit.org/b/151943 imported/blink/fast/events/click-with-large-negative-text-indent.html [ Failure ]
 webkit.org/b/151943 imported/blink/fast/replaced/image-map-alt-content.html [ Failure ]
index bcda3a0..1c06d53 100644 (file)
@@ -2712,6 +2712,9 @@ css3/line-break-language-sensitive [ Pass ImageOnlyFailure ]
 # Media controls tests are OS X only
 media/controls [ Skip ]
 
+# WIRELESS_PLAYBACK_TARGET not enabled.
+media/airplay-target-availability.html
+
 webkit.org/b/149128 fast/text/control-characters [ ImageOnlyFailure ]
 
 webkit.org/b/142548 editing/selection/extend-by-character-007.html [ Pass ]
index d7958e3..886e101 100644 (file)
@@ -1296,3 +1296,6 @@ webkit.org/b/152009 [ Debug ElCapitan ] fast/canvas/canvas-too-large-to-draw.htm
 # Temporarily disable font feature synthesis tests.
 [ Yosemite ElCapitan ] css3/font-variant-small-caps-synthesis.html [ ImageOnlyFailure ]
 [ Yosemite ElCapitan ] css3/font-variant-petite-caps-synthesis.html [ ImageOnlyFailure ]
+
+# WIRELESS_PLAYBACK_TARGET not enabled on Yosemite.
+[ Yosemite ] media/airplay-target-availability.html
index 7796dc4..1ad4992 100644 (file)
@@ -3321,6 +3321,9 @@ webkit.org/b/150948 [ Debug ] imported/blink/transitions/unprefixed-transform.ht
 
 webkit.org/b/151756 fast/css/pseudo-visited-background-color-on-input.html [ ImageOnlyFailure ]
 
+# WIRELESS_PLAYBACK_TARGET not enabled.
+media/airplay-target-availability.html
+
 webkit.org/b/152411 http/tests/contentdispositionattachmentsandbox/referer-header-stripped-with-meta-referer-always.html [ Failure ]
 webkit.org/b/152411 http/tests/contentdispositionattachmentsandbox/referer-header-stripped-with-meta-referer-default.html [ Failure ]
 webkit.org/b/152411 http/tests/contentdispositionattachmentsandbox/referer-header-stripped-with-meta-referer-never.html [ Failure ]
index e7ab40a..afd6437 100644 (file)
@@ -1,3 +1,38 @@
+2016-01-06  Eric Carlson  <eric.carlson@apple.com>
+
+        AirPlay route availability event not always sent
+        https://bugs.webkit.org/show_bug.cgi?id=152802
+
+        Reviewed by Jer Noble.
+
+        Test: media/airplay-target-availability.html
+
+        * Modules/mediasession/WebMediaSessionManager.cpp:
+        (WebCore::mediaProducerStateString): Log the new flags.
+        (WebCore::WebMediaSessionManager::clientStateDidChange): Schedule a client reconfiguration if
+          the 'requires monitoring', 'has listener', or 'has audio or video' flags have changed.
+        (WebCore::WebMediaSessionManager::configurePlaybackTargetMonitoring): Start monitoring if
+          at least one client has a listener and at least one has audio/video.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::mediaState): Set new flags.
+        * html/HTMLMediaElement.h:
+
+        * page/MediaProducer.h: Define new flags. Add new state enum.
+
+        * platform/graphics/MediaPlaybackTargetContext.h: Initial state is "Unknown".
+
+        * platform/mock/MediaPlaybackTargetMock.h:
+        * platform/mock/MediaPlaybackTargetPickerMock.cpp:
+        (WebCore::MediaPlaybackTargetPickerMock::externalOutputDeviceAvailable): Enums not bitfields.
+        (WebCore::MediaPlaybackTargetPickerMock::startingMonitoringPlaybackTargets): Ditto. Don't make
+          device change callback if the device state is "Unknown".
+        (WebCore::MediaPlaybackTargetPickerMock::setState): Ditto.
+        * platform/mock/MediaPlaybackTargetPickerMock.h:
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::setMockMediaPlaybackTargetPickerState): Support new state.
+
 2016-01-06  Brady Eidson  <beidson@apple.com>
 
         Modern IDB: storage/indexeddb/odd-strings.html is flaky.
index c6f6ec9..032bb63 100644 (file)
@@ -74,12 +74,16 @@ static String mediaProducerStateString(MediaProducer::MediaStateFlags flags)
         string.append("IsPlayingVideo + ");
     if (flags & MediaProducer::IsPlayingToExternalDevice)
         string.append("IsPlayingToExternalDevice + ");
+    if (flags & MediaProducer::HasPlaybackTargetAvailabilityListener)
+        string.append("HasPlaybackTargetAvailabilityListener + ");
     if (flags & MediaProducer::RequiresPlaybackTargetMonitoring)
         string.append("RequiresPlaybackTargetMonitoring + ");
     if (flags & MediaProducer::ExternalDeviceAutoPlayCandidate)
         string.append("ExternalDeviceAutoPlayCandidate + ");
     if (flags & MediaProducer::DidPlayToEnd)
         string.append("DidPlayToEnd + ");
+    if (flags & MediaProducer::HasAudioOrVideo)
+        string.append("HasAudioOrVideo + ");
     if (string.isEmpty())
         string.append("IsNotPlaying");
     else
@@ -209,7 +213,9 @@ void WebMediaSessionManager::clientStateDidChange(WebMediaSessionManagerClient&
     LOG(Media, "WebMediaSessionManager::clientStateDidChange(%p + %llu) - new flags = %s, old flags = %s", &client, contextId, mediaProducerStateString(newFlags).utf8().data(), mediaProducerStateString(oldFlags).utf8().data());
 
     changedClientState->flags = newFlags;
-    if (!flagsAreSet(oldFlags, MediaProducer::RequiresPlaybackTargetMonitoring) && flagsAreSet(newFlags, MediaProducer::RequiresPlaybackTargetMonitoring))
+
+    MediaProducer::MediaStateFlags updateConfigurationFlags = MediaProducer::RequiresPlaybackTargetMonitoring | MediaProducer::HasPlaybackTargetAvailabilityListener | MediaProducer::HasAudioOrVideo;
+    if (!flagsAreSet(oldFlags, updateConfigurationFlags) && flagsAreSet(newFlags, updateConfigurationFlags))
         scheduleDelayedTask(TargetMonitoringConfigurationTask);
 
     MediaProducer::MediaStateFlags playingToTargetFlags = MediaProducer::IsPlayingToExternalDevice | MediaProducer::IsPlayingVideo;
@@ -231,7 +237,7 @@ void WebMediaSessionManager::clientStateDidChange(WebMediaSessionManagerClient&
             return;
     }
 
-    // Do not take begin playing to the device unless playback has just started.
+    // Do not begin playing to the device unless playback has just started.
     if (!flagsAreSet(newFlags, MediaProducer::IsPlayingVideo) || flagsAreSet(oldFlags, MediaProducer::IsPlayingVideo))
         return;
 
@@ -339,16 +345,22 @@ void WebMediaSessionManager::configurePlaybackTargetClients()
 void WebMediaSessionManager::configurePlaybackTargetMonitoring()
 {
     bool monitoringRequired = false;
+    bool hasAvailabilityListener = false;
+    bool haveClientWithMedia = false;
     for (auto& state : m_clientState) {
         if (state->flags & MediaProducer::RequiresPlaybackTargetMonitoring) {
             monitoringRequired = true;
             break;
         }
+        if (state->flags & MediaProducer::HasPlaybackTargetAvailabilityListener)
+            hasAvailabilityListener = true;
+        if (state->flags & MediaProducer::HasAudioOrVideo)
+            haveClientWithMedia = true;
     }
 
     LOG(Media, "WebMediaSessionManager::configurePlaybackTargetMonitoring - monitoringRequired = %i", (int)monitoringRequired);
 
-    if (monitoringRequired)
+    if (monitoringRequired || (hasAvailabilityListener && haveClientWithMedia))
         targetPicker().startingMonitoringPlaybackTargets();
     else
         targetPicker().stopMonitoringPlaybackTargets();
index 81516fa..690691c 100644 (file)
@@ -6580,7 +6580,6 @@ void HTMLMediaElement::updateMediaState(UpdateMediaState updateState)
 
 MediaProducer::MediaStateFlags HTMLMediaElement::mediaState() const
 {
-
     MediaStateFlags state = IsNotPlaying;
 
     bool hasActiveVideo = isVideo() && hasVideo();
@@ -6589,13 +6588,19 @@ MediaProducer::MediaStateFlags HTMLMediaElement::mediaState() const
     if (m_player && m_player->isCurrentPlaybackTargetWireless())
         state |= IsPlayingToExternalDevice;
 
-    if (m_player && m_hasPlaybackTargetAvailabilityListeners && !m_mediaSession->wirelessVideoPlaybackDisabled(*this))
-        state |= RequiresPlaybackTargetMonitoring;
+    if (m_hasPlaybackTargetAvailabilityListeners) {
+        state |= HasPlaybackTargetAvailabilityListener;
+        if (!m_mediaSession->wirelessVideoPlaybackDisabled(*this))
+            state |= RequiresPlaybackTargetMonitoring;
+    }
 
     bool requireUserGesture = m_mediaSession->hasBehaviorRestriction(MediaElementSession::RequireUserGestureToAutoplayToExternalDevice);
     if (m_readyState >= HAVE_METADATA && !requireUserGesture && !m_failedToPlayToWirelessTarget)
         state |= ExternalDeviceAutoPlayCandidate;
 
+    if (hasActiveVideo || hasAudio)
+        state |= HasAudioOrVideo;
+
     if (hasActiveVideo && endedPlayback())
         state |= DidPlayToEnd;
 #endif
index 307eacb..6849a92 100644 (file)
@@ -759,6 +759,7 @@ private:
         Synchronously,
     };
     void updateMediaState(UpdateMediaState updateState = UpdateMediaState::Synchronously);
+    bool hasPlaybackTargetAvailabilityListeners() const { return m_hasPlaybackTargetAvailabilityListeners; }
 #endif
 
     void isVisibleInViewportChanged() override final;
index 9fcac4a..1fbb791 100644 (file)
@@ -41,6 +41,8 @@ public:
         IsSourceElementPlaying = 1 << 6,
         IsNextTrackControlEnabled = 1 << 7,
         IsPreviousTrackControlEnabled = 1 << 8,
+        HasPlaybackTargetAvailabilityListener = 1 << 9,
+        HasAudioOrVideo = 1 << 10,
     };
     typedef unsigned MediaStateFlags;
 
index 027fb68..1ce4a3e 100644 (file)
@@ -48,8 +48,9 @@ public:
     };
 
     enum ContextState {
-        Unavailable = 0,
-        OutputDeviceAvailable = 1 << 0,
+        Unknown = 0,
+        OutputDeviceUnavailable = 1,
+        OutputDeviceAvailable = 2,
     };
     typedef unsigned State;
 
@@ -97,7 +98,7 @@ private:
     Type m_type { None };
     AVOutputContext *m_outputContext { nullptr };
     String m_name;
-    State m_state { Unavailable };
+    State m_state { Unknown };
 };
 
 }
index 9468115..9625518 100644 (file)
@@ -55,7 +55,7 @@ protected:
     MediaPlaybackTargetMock(const String&, MediaPlaybackTargetContext::State);
 
     String m_name;
-    MediaPlaybackTargetContext::State m_state { MediaPlaybackTargetContext::Unavailable };
+    MediaPlaybackTargetContext::State m_state { MediaPlaybackTargetContext::Unknown };
     mutable MediaPlaybackTargetContext m_context;
 };
 
index c6fdfed..7128148 100644 (file)
@@ -60,7 +60,7 @@ MediaPlaybackTargetPickerMock::~MediaPlaybackTargetPickerMock()
 bool MediaPlaybackTargetPickerMock::externalOutputDeviceAvailable()
 {
     LOG(Media, "MediaPlaybackTargetPickerMock::externalOutputDeviceAvailable");
-    return m_state & MediaPlaybackTargetContext::OutputDeviceAvailable;
+    return m_state == MediaPlaybackTargetContext::OutputDeviceAvailable;
 }
 
 Ref<MediaPlaybackTarget> MediaPlaybackTargetPickerMock::playbackTarget()
@@ -93,10 +93,10 @@ void MediaPlaybackTargetPickerMock::startingMonitoringPlaybackTargets()
 {
     LOG(Media, "MediaPlaybackTargetPickerMock::startingMonitoringPlaybackTargets");
 
-    if (m_state & MediaPlaybackTargetContext::OutputDeviceAvailable)
+    if (m_state == MediaPlaybackTargetContext::OutputDeviceAvailable)
         availableDevicesDidChange();
 
-    if (!m_deviceName.isEmpty())
+    if (!m_deviceName.isEmpty() && m_state != MediaPlaybackTargetContext::Unknown)
         currentDeviceDidChange();
 }
 
@@ -108,14 +108,14 @@ void MediaPlaybackTargetPickerMock::stopMonitoringPlaybackTargets()
 void MediaPlaybackTargetPickerMock::invalidatePlaybackTargets()
 {
     LOG(Media, "MediaPlaybackTargetPickerMock::invalidatePlaybackTargets");
-    setState(WTF::emptyString(), MediaPlaybackTargetContext::Unavailable);
+    setState(emptyString(), MediaPlaybackTargetContext::Unknown);
 }
 
 void MediaPlaybackTargetPickerMock::setState(const String& deviceName, MediaPlaybackTargetContext::State state)
 {
     LOG(Media, "MediaPlaybackTargetPickerMock::setState - name = %s, state = 0x%x", deviceName.utf8().data(), (unsigned)state);
 
-    if (deviceName != m_deviceName) {
+    if (deviceName != m_deviceName && state != MediaPlaybackTargetContext::Unknown) {
         m_deviceName = deviceName;
         currentDeviceDidChange();
     }
index 615959b..714834d 100644 (file)
@@ -58,7 +58,7 @@ private:
 
     String m_deviceName;
     RunLoop::Timer<MediaPlaybackTargetPickerMock> m_timer;
-    MediaPlaybackTargetContext::State m_state { MediaPlaybackTargetContext::Unavailable };
+    MediaPlaybackTargetContext::State m_state { MediaPlaybackTargetContext::Unknown };
     bool m_showingMenu { false };
 };
 
index 329a7d8..604a7aa 100644 (file)
@@ -2977,10 +2977,14 @@ void Internals::setMockMediaPlaybackTargetPickerState(const String& deviceName,
     Page* page = contextDocument()->frame()->page();
     ASSERT(page);
 
-    MediaPlaybackTargetContext::State state = MediaPlaybackTargetContext::Unavailable;
+    MediaPlaybackTargetContext::State state = MediaPlaybackTargetContext::Unknown;
 
     if (equalIgnoringCase(deviceState, "DeviceAvailable"))
         state = MediaPlaybackTargetContext::OutputDeviceAvailable;
+    else if (equalIgnoringCase(deviceState, "DeviceUnavailable"))
+        state = MediaPlaybackTargetContext::OutputDeviceUnavailable;
+    else if (equalIgnoringCase(deviceState, "Unknown"))
+        state = MediaPlaybackTargetContext::Unknown;
     else {
         ec = INVALID_ACCESS_ERR;
         return;