Refine MediaSession interruptions
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 4 Feb 2014 14:59:18 +0000 (14:59 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 4 Feb 2014 14:59:18 +0000 (14:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=128125

Reviewed by Jer Noble.

Source/WebCore:

Test: media/video-background-playback.html

* WebCore.exp.in: Export applicationWillEnterForeground and applicationWillEnterBackground for
    Internals.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::play): Ask the media session if playback is allowed instead of check
    to see if it is interrupted directly.
(WebCore::HTMLMediaElement::pause): Ask the media session if pausing is allowed instead of check
    to see if it is interrupted directly.
(WebCore::HTMLMediaElement::mediaType): Return media type based on media characteristics once
    the information is available.
(WebCore::HTMLMediaElement::resumePlayback): New.
* html/HTMLMediaElement.h:

* html/HTMLMediaSession.cpp:
(WebCore::restrictionName): New, use for logging only.
(WebCore::HTMLMediaSession::addBehaviorRestriction): Log  restriction changes.
(WebCore::HTMLMediaSession::removeBehaviorRestriction): Ditto.
* html/HTMLMediaSession.h:

* platform/audio/MediaSession.cpp:
(WebCore::stateName): New, used for logging.
(WebCore::MediaSession::MediaSession): Don't cache client media type because it can change.
(WebCore::MediaSession::setState): Log state changes.
(WebCore::MediaSession::beginInterruption): Remember the current state in case we want to use it
    to restore state when the interruption ends.
(WebCore::MediaSession::endInterruption): Resume playback if appropriate.
(WebCore::MediaSession::clientWillBeginPlayback): Track the client's playback state.
(WebCore::MediaSession::clientWillPausePlayback): Ditto.
(WebCore::MediaSession::mediaType): Ask client for state.
* platform/audio/MediaSession.h:

* platform/audio/MediaSessionManager.cpp:
(WebCore::MediaSessionManager::MediaSessionManager): m_interruptions -> m_interrupted.
(WebCore::MediaSessionManager::beginInterruption): Don't assume interruptions are always balanced.
(WebCore::MediaSessionManager::endInterruption): Ditto.
(WebCore::MediaSessionManager::addSession):
(WebCore::MediaSessionManager::applicationWillEnterBackground): Interrupt client if it is not
    allowed to play in the background.
(WebCore::MediaSessionManager::applicationWillEnterForeground): End client interruption if it
    was stopped by an interruption.
* platform/audio/MediaSessionManager.h:

* platform/audio/ios/MediaSessionManagerIOS.h:
* platform/audio/ios/MediaSessionManagerIOS.mm:
(WebCore::MediaSessionManageriOS::~MediaSessionManageriOS): Clear the helper callback.
(WebCore::MediaSessionManageriOS::resetRestrictions): Mark video as not allowed to play
    while the application is in the background. Register for application suspend/resume
    notifications.
(-[WebMediaSessionHelper clearCallback]): Set _callback to nil.
(-[WebMediaSessionHelper applicationWillEnterForeground:]): New, notify client of application
    state change.
(-[WebMediaSessionHelper applicationWillResignActive:]): Ditto.

* platform/audio/mac/AudioDestinationMac.h: Add resumePlayback.

* testing/Internals.cpp:
(WebCore::Internals::applicationWillEnterForeground): New, simulate application context switch.
(WebCore::Internals::applicationWillEnterBackground): Ditto.
(WebCore::Internals::setMediaSessionRestrictions): Add "BackgroundPlaybackNotPermitted" restriction.
* testing/Internals.h:
* testing/Internals.idl:

Source/WebKit:

* WebKit.vcxproj/WebKitExportGenerator/WebKitExports.def.in: Export applicationWillEnterForeground
    and applicationWillEnterBackground for Internals.

LayoutTests:

* media/video-background-playback-expected.txt: Added.
* media/video-background-playback.html: Added.
* media/video-interruption-active-when-element-created-expected.txt: Removed.
* media/video-interruption-active-when-element-created.html: Removed.
* media/video-interruption-with-resume-allowing-play-expected.txt:
* media/video-interruption-with-resume-allowing-play.html:
* media/video-interruption-with-resume-not-allowing-play-expected.txt:
* media/video-interruption-with-resume-not-allowing-play.html:

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

27 files changed:
LayoutTests/ChangeLog
LayoutTests/media/video-background-playback-expected.txt [new file with mode: 0644]
LayoutTests/media/video-background-playback.html [new file with mode: 0644]
LayoutTests/media/video-interruption-active-when-element-created-expected.txt [deleted file]
LayoutTests/media/video-interruption-active-when-element-created.html [deleted file]
LayoutTests/media/video-interruption-with-resume-allowing-play-expected.txt
LayoutTests/media/video-interruption-with-resume-allowing-play.html
LayoutTests/media/video-interruption-with-resume-not-allowing-play-expected.txt
LayoutTests/media/video-interruption-with-resume-not-allowing-play.html
Source/WebCore/ChangeLog
Source/WebCore/WebCore.exp.in
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.h
Source/WebCore/html/HTMLMediaSession.cpp
Source/WebCore/html/HTMLMediaSession.h
Source/WebCore/platform/audio/MediaSession.cpp
Source/WebCore/platform/audio/MediaSession.h
Source/WebCore/platform/audio/MediaSessionManager.cpp
Source/WebCore/platform/audio/MediaSessionManager.h
Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.h
Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.mm
Source/WebCore/platform/audio/mac/AudioDestinationMac.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl
Source/WebKit/ChangeLog
Source/WebKit/WebKit.vcxproj/WebKitExportGenerator/WebKitExports.def.in

index d692bde..8e10769 100644 (file)
@@ -1,3 +1,19 @@
+2014-02-04  Eric Carlson  <eric.carlson@apple.com>
+
+        Refine MediaSession interruptions
+        https://bugs.webkit.org/show_bug.cgi?id=128125
+
+        Reviewed by Jer Noble.
+
+        * media/video-background-playback-expected.txt: Added.
+        * media/video-background-playback.html: Added.
+        * media/video-interruption-active-when-element-created-expected.txt: Removed.
+        * media/video-interruption-active-when-element-created.html: Removed.
+        * media/video-interruption-with-resume-allowing-play-expected.txt:
+        * media/video-interruption-with-resume-allowing-play.html:
+        * media/video-interruption-with-resume-not-allowing-play-expected.txt:
+        * media/video-interruption-with-resume-not-allowing-play.html:
+
 2014-02-04  Andrzej Badowski   <a.badowski@samsung.com>
 
         [EFL] Add expectations for three flaky media layout tests
diff --git a/LayoutTests/media/video-background-playback-expected.txt b/LayoutTests/media/video-background-playback-expected.txt
new file mode 100644 (file)
index 0000000..dcca06b
--- /dev/null
@@ -0,0 +1,33 @@
+  
+Test switching application state when <video> is not allowed to play in background.
+
+RUN(internals.setMediaSessionRestrictions('video', 'BackgroundPlaybackNotPermitted'))
+
+EVENT(canplaythrough)
+EVENT(canplaythrough)
+
+RUN(audio.play())
+RUN(video.play())
+EVENT(playing)
+EVENT(playing)
+
+** Simulate switch to background, video should pause.
+RUN(internals.applicationWillEnterBackground())
+
+EVENT(pause)
+
+** 100ms timer fired...
+EXPECTED (video.paused == 'true') OK
+EXPECTED (audio.paused == 'false') OK
+
+** Simulate switch back to foreground, video should resume.
+RUN(internals.applicationWillEnterForeground())
+
+EVENT(playing)
+
+** 100ms timer fired...
+EXPECTED (video.paused == 'false') OK
+EXPECTED (audio.paused == 'false') OK
+
+END OF TEST
+
diff --git a/LayoutTests/media/video-background-playback.html b/LayoutTests/media/video-background-playback.html
new file mode 100644 (file)
index 0000000..7692b2e
--- /dev/null
@@ -0,0 +1,93 @@
+<html>
+    <head>
+        <script src=media-file.js></script>
+        <script src=video-test.js></script>
+        <script>
+            var playCount = 0;
+            var playThroughCount = 0;
+            var state = 0;
+            var audio;
+            var video
+
+            function logEvent(evt)
+            {
+                consoleWrite("EVENT(" + evt.type + ")");
+            }
+
+            function checkState()
+            {
+                consoleWrite("<br>** 100ms timer fired...");
+                switch (state) {
+                case "background":
+                    testExpected("video.paused", true);
+                    testExpected("audio.paused", false);
+                    state = "foreground";
+                    consoleWrite("<br>** Simulate switch back to foreground, video should resume.");
+                    run("internals.applicationWillEnterForeground()");
+                    setTimeout(checkState, 100);
+                    consoleWrite("");
+                    break;
+                case "foreground":
+                    testExpected("video.paused", false);
+                    testExpected("audio.paused", false);
+                    consoleWrite("");
+                    endTest();
+                    break;
+                }
+            }
+
+            function playing(evt)
+            {
+                logEvent(evt);
+                if (++playCount != 2)
+                    return;
+
+                consoleWrite("<br>** Simulate switch to background, video should pause.");
+                run("internals.applicationWillEnterBackground()");
+                setTimeout(checkState, 100);
+                state = "background";
+                consoleWrite("");
+            }
+
+            function canplaythrough(evt)
+            {
+                logEvent(evt);
+                if (++playThroughCount < 2)
+                    return;
+                consoleWrite("");
+                run("audio.play()");
+                run("video.play()");
+            }
+
+            function start()
+            {
+                if (!window.internals) {
+                    failTest('This test requires window.internals.');
+                    return;
+                }
+
+                var elements = document.getElementsByTagName('video');
+                for (var i = 0; i < elements.length; ++i) {
+                    elements[i].addEventListener("canplaythrough", canplaythrough);
+                    elements[i].addEventListener('playing', playing);
+                    elements[i].addEventListener('pause', logEvent);
+                }
+                video = elements[0];
+                video.src = findMediaFile("video", "content/test");
+
+                audio = elements[1];
+                audio.src = findMediaFile("audio", "content/silence");
+
+                run("internals.setMediaSessionRestrictions('video', 'BackgroundPlaybackNotPermitted')");
+                state = "foreground";
+                consoleWrite("");
+            }
+        </script>
+    </head>
+
+    <body onload="start()">
+        <video controls id="one"></video>
+        <video controls id="two"></video>
+        <p>Test switching application state when &lt;video&gt; is not allowed to play in background.</p>
+    </body>
+</html>
diff --git a/LayoutTests/media/video-interruption-active-when-element-created-expected.txt b/LayoutTests/media/video-interruption-active-when-element-created-expected.txt
deleted file mode 100644 (file)
index 42e082c..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-Test a <video> element crated during interruption behaves correctly.
-
-RUN(internals.beginMediaSessionInterruption())
-RUN(video = document.createElement('video'))
-RUN(document.body.appendChild(video))
-EVENT(canplaythrough)
-
-RUN(video.play())
-100ms timer fired...
-EXPECTED (video.paused == 'true') OK
-RUN(internals.endMediaSessionInterruption('MayResumePlaying'))
-EVENT(playing)
-
-END OF TEST
-
diff --git a/LayoutTests/media/video-interruption-active-when-element-created.html b/LayoutTests/media/video-interruption-active-when-element-created.html
deleted file mode 100644 (file)
index 5c69ab0..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-<html>
-    <head>
-        <script src=media-file.js></script>
-        <script src=video-test.js></script>
-        <script>
-            var state = 0;
-
-            function checkState()
-            {
-                consoleWrite("100ms timer fired...");
-                testExpected("video.paused", true);
-                state = "resuming";
-                run("internals.endMediaSessionInterruption('MayResumePlaying')");
-            }
-
-            function playing()
-            {
-                if (state != "resuming")
-                {
-                    consoleWrite("");
-                    failTest("<b>Playback started during interruption.</b>");
-                    return;
-                }
-
-                consoleWrite("");
-                endTest();
-            }
-
-            function canplaythrough()
-            {
-                consoleWrite("");
-                run("video.play()");
-                setTimeout(checkState, 100);
-            }
-
-            function start()
-            {
-                if (!window.internals) {
-                    failTest('This test requires window.internals.');
-                    return;
-                }
-                run("internals.beginMediaSessionInterruption()");;
-                run("video = document.createElement('video')");
-                run("document.body.appendChild(video)");
-                waitForEvent('canplaythrough', canplaythrough);
-                waitForEvent('playing', playing);
-                video.src = findMediaFile("video", "content/test");
-                state = "interrupted";
-            }
-        </script>
-    </head>
-
-    <body onload="start()">
-        <p>Test a &lt;video&gt; element crated during interruption behaves correctly.</p>
-    </body>
-</html>
index b6b91a2..747c775 100644 (file)
@@ -2,13 +2,18 @@
 Test that play() during interruption does nothing, ending interruption allows playback to resume.
 
 EVENT(canplaythrough)
+RUN(video.play())
 
+EVENT(playing)
+EXPECTED (video.paused == 'false') OK
 RUN(internals.beginMediaSessionInterruption())
-RUN(video.play())
+
 100ms timer fired...
 EXPECTED (video.paused == 'true') OK
 RUN(internals.endMediaSessionInterruption('MayResumePlaying'))
+
 EVENT(playing)
+EXPECTED (video.paused == 'false') OK
 
 END OF TEST
 
index 0da21c8..85213f0 100644 (file)
@@ -7,33 +7,34 @@
 
             function checkState()
             {
-                consoleWrite("100ms timer fired...");
-                testExpected("video.paused", true);
-                state = "resuming";
-                run("internals.endMediaSessionInterruption('MayResumePlaying')");
-            }
-
-            function playing()
-            {
-                if (state != "resuming")
-                {
+                switch (state) {
+                case "playing":
+                    testExpected("video.paused", false);
+                    state = "interrupted";
+                    run("internals.beginMediaSessionInterruption()");;
+                    setTimeout(checkState, 100);
                     consoleWrite("");
-                    failTest("<b>Playback started during interruption.</b>");
-                    return;
+                    break;
+                case "interrupted":
+                    consoleWrite("100ms timer fired...");
+                    testExpected("video.paused", true);
+                    state = "resuming";
+                    run("internals.endMediaSessionInterruption('MayResumePlaying')");
+                    consoleWrite("");
+                    break;
+                case "resuming":
+                    testExpected("video.paused", false);
+                    consoleWrite("");
+                    endTest();
+                    break;
                 }
-
-                consoleWrite("");
-                endTest();
             }
 
             function canplaythrough()
             {
-                consoleWrite("");
-
-                run("internals.beginMediaSessionInterruption()");;
-                state = "interrupted";
+                state = "playing";
                 run("video.play()");
-                setTimeout(checkState, 100);
+                consoleWrite("");
             }
 
             function start()
@@ -45,7 +46,7 @@
 
                 findMediaElement();
                 waitForEvent('canplaythrough', canplaythrough);
-                waitForEvent('playing', playing);
+                waitForEvent('playing', checkState);
                 video.src = findMediaFile("video", "content/test");
             }
         </script>
index 77f1788..f927697 100644 (file)
@@ -1,13 +1,16 @@
 
-Test that play() during interruption does nothing, ending interruption does not allow playback to resume.
+Test that playback is paused by an interruption, and that ending the interruption does automatically resume playback.
 
 EVENT(canplaythrough)
+RUN(video.play())
 
+EVENT(playing)
 RUN(internals.beginMediaSessionInterruption())
-RUN(video.play())
+
 100ms timer fired...
 EXPECTED (video.paused == 'true') OK
 RUN(internals.endMediaSessionInterruption(''))
+
 100ms timer fired...
 EXPECTED (video.paused == 'true') OK
 
index ea5a188..a250bee 100644 (file)
@@ -7,15 +7,13 @@
 
             function playing()
             {
-                if (state == "resuming")
-                    failTest("<b>Playback started after interruption.</b>");
-                else
-                    failTest("<b>Playback started during interruption.</b>");
+                run("internals.beginMediaSessionInterruption()");;
+                setTimeout(checkState, 100);
             }
 
             function checkState()
             {
-                consoleWrite("100ms timer fired...");
+                consoleWrite("<br>100ms timer fired...");
                 testExpected("video.paused", true);
                 switch (state) {
                 case "interrupted":
 
             function canplaythrough()
             {
-                consoleWrite("");
-
-                run("internals.beginMediaSessionInterruption()");;
                 state = "interrupted";
                 run("video.play()");
-                setTimeout(checkState, 100);
+                consoleWrite("");
             }
 
             function start()
@@ -57,6 +52,6 @@
 
     <body onload="start()">
         <video controls ></video>
-        <p>Test that play() during interruption does nothing, ending interruption does not allow playback to resume.</p>
+        <p>Test that playback is paused by an interruption, and that ending the interruption does automatically resume playback.</p>
     </body>
 </html>
index ac5e091..55615fb 100644 (file)
@@ -1,3 +1,74 @@
+2014-02-04  Eric Carlson  <eric.carlson@apple.com>
+
+        Refine MediaSession interruptions
+        https://bugs.webkit.org/show_bug.cgi?id=128125
+
+        Reviewed by Jer Noble.
+
+        Test: media/video-background-playback.html
+
+        * WebCore.exp.in: Export applicationWillEnterForeground and applicationWillEnterBackground for
+            Internals.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::play): Ask the media session if playback is allowed instead of check
+            to see if it is interrupted directly.
+        (WebCore::HTMLMediaElement::pause): Ask the media session if pausing is allowed instead of check
+            to see if it is interrupted directly.
+        (WebCore::HTMLMediaElement::mediaType): Return media type based on media characteristics once
+            the information is available.
+        (WebCore::HTMLMediaElement::resumePlayback): New.
+        * html/HTMLMediaElement.h:
+
+        * html/HTMLMediaSession.cpp:
+        (WebCore::restrictionName): New, use for logging only.
+        (WebCore::HTMLMediaSession::addBehaviorRestriction): Log  restriction changes.
+        (WebCore::HTMLMediaSession::removeBehaviorRestriction): Ditto.
+        * html/HTMLMediaSession.h:
+
+        * platform/audio/MediaSession.cpp:
+        (WebCore::stateName): New, used for logging.
+        (WebCore::MediaSession::MediaSession): Don't cache client media type because it can change.
+        (WebCore::MediaSession::setState): Log state changes.
+        (WebCore::MediaSession::beginInterruption): Remember the current state in case we want to use it
+            to restore state when the interruption ends.
+        (WebCore::MediaSession::endInterruption): Resume playback if appropriate.
+        (WebCore::MediaSession::clientWillBeginPlayback): Track the client's playback state.
+        (WebCore::MediaSession::clientWillPausePlayback): Ditto.
+        (WebCore::MediaSession::mediaType): Ask client for state.
+        * platform/audio/MediaSession.h:
+
+        * platform/audio/MediaSessionManager.cpp:
+        (WebCore::MediaSessionManager::MediaSessionManager): m_interruptions -> m_interrupted.
+        (WebCore::MediaSessionManager::beginInterruption): Don't assume interruptions are always balanced.
+        (WebCore::MediaSessionManager::endInterruption): Ditto.
+        (WebCore::MediaSessionManager::addSession): 
+        (WebCore::MediaSessionManager::applicationWillEnterBackground): Interrupt client if it is not
+            allowed to play in the background.
+        (WebCore::MediaSessionManager::applicationWillEnterForeground): End client interruption if it
+            was stopped by an interruption.
+        * platform/audio/MediaSessionManager.h:
+
+        * platform/audio/ios/MediaSessionManagerIOS.h:
+        * platform/audio/ios/MediaSessionManagerIOS.mm:
+        (WebCore::MediaSessionManageriOS::~MediaSessionManageriOS): Clear the helper callback.
+        (WebCore::MediaSessionManageriOS::resetRestrictions): Mark video as not allowed to play
+            while the application is in the background. Register for application suspend/resume
+            notifications.
+        (-[WebMediaSessionHelper clearCallback]): Set _callback to nil.
+        (-[WebMediaSessionHelper applicationWillEnterForeground:]): New, notify client of application 
+            state change.
+        (-[WebMediaSessionHelper applicationWillResignActive:]): Ditto.
+
+        * platform/audio/mac/AudioDestinationMac.h: Add resumePlayback.
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::applicationWillEnterForeground): New, simulate application context switch.
+        (WebCore::Internals::applicationWillEnterBackground): Ditto.
+        (WebCore::Internals::setMediaSessionRestrictions): Add "BackgroundPlaybackNotPermitted" restriction.
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2014-02-04  Mihai Maerean  <mmaerean@adobe.com>
 
         [CSS Regions] Fix Assert SHOULD NEVER BE REACHED in RenderLayer::enclosingElement()
index dfcaf55..3f61973 100644 (file)
@@ -760,6 +760,8 @@ __ZN7WebCore19MediaSessionManager14addRestrictionENS_12MediaSession9MediaTypeEj
 __ZN7WebCore19MediaSessionManager15endInterruptionENS_12MediaSession20EndInterruptionFlagsE
 __ZN7WebCore19MediaSessionManager17beginInterruptionEv
 __ZN7WebCore19MediaSessionManager17removeRestrictionENS_12MediaSession9MediaTypeEj
+__ZNK7WebCore19MediaSessionManager30applicationWillEnterBackgroundEv
+__ZNK7WebCore19MediaSessionManager30applicationWillEnterForegroundEv
 __ZN7WebCore19ResourceRequestBase11setHTTPBodyEN3WTF10PassRefPtrINS_8FormDataEEE
 __ZN7WebCore19ResourceRequestBase13setHTTPMethodERKN3WTF6StringE
 __ZN7WebCore19ResourceRequestBase18setHTTPHeaderFieldEPKcRKN3WTF6StringE
index 9f997b0..5563039 100644 (file)
@@ -2648,8 +2648,8 @@ void HTMLMediaElement::play()
     if (ScriptController::processingUserGesture())
         removeBehaviorsRestrictionsAfterFirstUserGesture();
 
-    if (m_mediaSession->state() == MediaSession::Interrupted) {
-        m_resumePlaybackAfterInterruption = true;
+    if (!m_mediaSession->clientWillBeginPlayback()) {
+        LOG(Media, "  returning because of interruption");
         return;
     }
     
@@ -2695,11 +2695,11 @@ void HTMLMediaElement::pause()
     if (!m_mediaSession->playbackPermitted(*this))
         return;
 
-    if (m_mediaSession->state() == MediaSession::Interrupted) {
-        m_resumePlaybackAfterInterruption = false;
+    if (!m_mediaSession->clientWillPausePlayback()) {
+        LOG(Media, "  returning because of interruption");
         return;
     }
-    
+
     pauseInternal();
 }
 
@@ -4268,8 +4268,6 @@ void HTMLMediaElement::updatePlayState()
         invalidateCachedTime();
 
         if (playerPaused) {
-            m_mediaSession->clientWillBeginPlayback();
-
             if (m_mediaSession->requiresFullscreenForVideoPlayback(*this))
                 enterFullscreen();
 
@@ -4289,7 +4287,7 @@ void HTMLMediaElement::updatePlayState()
         startPlaybackProgressTimer();
         m_playing = true;
 
-    } else { // Should not be playing right now
+    } else {
         if (!playerPaused)
             m_player->pause();
         refreshCachedTime();
@@ -5762,39 +5760,27 @@ unsigned long long HTMLMediaElement::fileSize() const
 
 MediaSession::MediaType HTMLMediaElement::mediaType() const
 {
+    if (m_player && m_readyState >= HAVE_METADATA)
+        return hasVideo() ? MediaSession::Video : MediaSession::Audio;
+
     if (hasTagName(HTMLNames::videoTag))
         return MediaSession::Video;
 
     return MediaSession::Audio;
 }
 
-void HTMLMediaElement::beginInterruption()
+void HTMLMediaElement::pausePlayback()
 {
-    LOG(Media, "HTMLMediaElement::beginInterruption");
-    
-    m_resumePlaybackAfterInterruption = !paused();
-    if (m_resumePlaybackAfterInterruption)
+    if (!paused())
         pause();
 }
 
-void HTMLMediaElement::endInterruption(MediaSession::EndInterruptionFlags flags)
+void HTMLMediaElement::resumePlayback()
 {
-    bool shouldResumePlayback = m_resumePlaybackAfterInterruption;
-    m_resumePlaybackAfterInterruption = false;
-
-    if (!flags & MediaSession::MayResumePlaying)
-        return;
-
-    if (shouldResumePlayback)
+    if (paused())
         play();
 }
 
-void HTMLMediaElement::pausePlayback()
-{
-    if (!paused())
-        pause();
-}
-
 }
 
 #endif
index 7ca5e0e..a51366f 100644 (file)
@@ -669,10 +669,8 @@ private:
 #endif
 
     virtual MediaSession::MediaType mediaType() const override;
-
-    virtual void beginInterruption() override;
-    virtual void endInterruption(MediaSession::EndInterruptionFlags) override;
     virtual void pausePlayback() override;
+    virtual void resumePlayback() override;
 
     Timer<HTMLMediaElement> m_loadTimer;
     Timer<HTMLMediaElement> m_progressEventTimer;
@@ -780,8 +778,6 @@ private:
     bool m_platformLayerBorrowed : 1;
 #endif
 
-    bool m_resumePlaybackAfterInterruption : 1;
-
 #if ENABLE(VIDEO_TRACK)
     bool m_tracksAreReady : 1;
     bool m_haveVisibleTextTrack : 1;
index 2978d67..07b01fa 100644 (file)
 
 namespace WebCore {
 
+#if !LOG_DISABLED
+static const char* restrictionName(HTMLMediaSession::BehaviorRestrictions restriction)
+{
+#define CASE(_restriction) case HTMLMediaSession::_restriction: return #_restriction; break;
+    switch (restriction) {
+    CASE(NoRestrictions);
+    CASE(RequireUserGestureForLoad);
+    CASE(RequireUserGestureForRateChange);
+    CASE(RequireUserGestureForFullscreen);
+    CASE(RequirePageConsentToLoadMedia);
+    CASE(RequirePageConsentToResumeMedia);
+#if ENABLE(IOS_AIRPLAY)
+    CASE(RequireUserGestureToShowPlaybackTargetPicker);
+#endif
+    }
+
+    ASSERT_NOT_REACHED();
+    return "";
+}
+#endif
+
 static void initializeAudioSession()
 {
 #if PLATFORM(IOS)
@@ -71,11 +92,13 @@ HTMLMediaSession::HTMLMediaSession(MediaSessionClient& client)
 
 void HTMLMediaSession::addBehaviorRestriction(BehaviorRestrictions restriction)
 {
+    LOG(Media, "HTMLMediaSession::addBehaviorRestriction - adding %s", restrictionName(restriction));
     m_restrictions |= restriction;
 }
 
 void HTMLMediaSession::removeBehaviorRestriction(BehaviorRestrictions restriction)
 {
+    LOG(Media, "HTMLMediaSession::removeBehaviorRestriction - removing %s", restrictionName(restriction));
     m_restrictions &= ~restriction;
 }
 
@@ -159,11 +182,6 @@ MediaPlayer::Preload HTMLMediaSession::effectivePreloadForElement(const HTMLMedi
     return preload;
 }
 
-void HTMLMediaSession::clientWillBeginPlayback() const
-{
-    MediaSessionManager::sharedManager().sessionWillBeginPlayback(*this);
-}
-
 bool HTMLMediaSession::requiresFullscreenForVideoPlayback(const HTMLMediaElement& element) const
 {
     if (!MediaSessionManager::sharedManager().sessionRestrictsInlineVideoPlayback(*this))
index 55ea520..07ea67a 100644 (file)
@@ -42,8 +42,6 @@ public:
     HTMLMediaSession(MediaSessionClient&);
     virtual ~HTMLMediaSession() { }
 
-    void clientWillBeginPlayback() const;
-
     bool playbackPermitted(const HTMLMediaElement&) const;
     bool dataLoadingPermitted(const HTMLMediaElement&) const;
     bool fullscreenPermitted(const HTMLMediaElement&) const;
index a35d192..2db7f05 100644 (file)
 
 namespace WebCore {
 
+#if !LOG_DISABLED
+static const char* stateName(MediaSession::State state)
+{
+#define CASE(_state) case MediaSession::_state: return #_state; break;
+    switch (state) {
+    CASE(Idle);
+    CASE(Playing);
+    CASE(Paused);
+    CASE(Interrupted);
+    }
+
+    ASSERT_NOT_REACHED();
+    return "";
+}
+#endif
+    
 std::unique_ptr<MediaSession> MediaSession::create(MediaSessionClient& client)
 {
     return std::make_unique<MediaSession>(client);
@@ -39,10 +55,11 @@ std::unique_ptr<MediaSession> MediaSession::create(MediaSessionClient& client)
 
 MediaSession::MediaSession(MediaSessionClient& client)
     : m_client(client)
-    , m_state(Running)
+    , m_state(Idle)
+    , m_stateToRestore(Idle)
+    , m_notifyingClient(false)
 {
-    m_type = m_client.mediaType();
-    ASSERT(m_type >= None && m_type <= WebAudio);
+    ASSERT(m_client.mediaType() >= None && m_client.mediaType() <= WebAudio);
     MediaSessionManager::sharedManager().addSession(*this);
 }
 
@@ -51,18 +68,52 @@ MediaSession::~MediaSession()
     MediaSessionManager::sharedManager().removeSession(*this);
 }
 
+void MediaSession::setState(State state)
+{
+    LOG(Media, "MediaSession::setState - %s", stateName(state));
+    m_state = state;
+}
+
 void MediaSession::beginInterruption()
 {
     LOG(Media, "MediaSession::beginInterruption");
-    m_state = Interrupted;
-    m_client.beginInterruption();
+
+    m_stateToRestore = state();
+    m_notifyingClient = true;
+    client().pausePlayback();
+    setState(Interrupted);
+    m_notifyingClient = false;
 }
 
 void MediaSession::endInterruption(EndInterruptionFlags flags)
 {
-    LOG(Media, "MediaSession::endInterruption");
-    m_state = Running;
-    m_client.endInterruption(flags);
+    LOG(Media, "MediaSession::endInterruption - flags = %i", (int)flags);
+
+    setState(Paused);
+    m_stateToRestore = Idle;
+    if (flags & MayResumePlaying && m_stateToRestore == Playing) {
+        LOG(Media, "MediaSession::endInterruption - resuming playback");
+        client().resumePlayback();
+    }
+}
+
+bool MediaSession::clientWillBeginPlayback()
+{
+    setState(Playing);
+    MediaSessionManager::sharedManager().sessionWillBeginPlayback(*this);
+    return true;
+}
+
+bool MediaSession::clientWillPausePlayback()
+{
+    if (state() == Interrupted) {
+        if (!m_notifyingClient)
+            m_stateToRestore = Paused;
+        return false;
+    }
+    
+    setState(Paused);
+    return true;
 }
 
 void MediaSession::pauseSession()
@@ -71,4 +122,9 @@ void MediaSession::pauseSession()
     m_client.pausePlayback();
 }
 
+MediaSession::MediaType MediaSession::mediaType() const
+{
+    return m_client.mediaType();
+}
+    
 }
index b53fead..779c7a0 100644 (file)
@@ -45,15 +45,16 @@ public:
         Audio,
         WebAudio,
     };
-    
-    MediaType mediaType() const { return m_type; }
+    MediaType mediaType() const;
 
     enum State {
-        Running,
+        Idle,
+        Playing,
+        Paused,
         Interrupted,
     };
     State state() const { return m_state; }
-    void setState(State state) { m_state = state; }
+    void setState(State);
 
     enum EndInterruptionFlags {
         NoFlags = 0,
@@ -62,6 +63,12 @@ public:
     void beginInterruption();
     void endInterruption(EndInterruptionFlags);
 
+    void applicationWillEnterForeground() const;
+    void applicationWillEnterBackground() const;
+
+    bool clientWillBeginPlayback();
+    bool clientWillPausePlayback();
+
     void pauseSession();
 
 protected:
@@ -69,8 +76,9 @@ protected:
 
 private:
     MediaSessionClient& m_client;
-    MediaType m_type;
     State m_state;
+    State m_stateToRestore;
+    bool m_notifyingClient;
 };
 
 class MediaSessionClient {
@@ -79,10 +87,7 @@ public:
     MediaSessionClient() { }
     
     virtual MediaSession::MediaType mediaType() const = 0;
-    
-    virtual void beginInterruption() { }
-    virtual void endInterruption(MediaSession::EndInterruptionFlags) { }
-
+    virtual void resumePlayback() = 0;
     virtual void pausePlayback() = 0;
 
 protected:
index f049c67..db59ba4 100644 (file)
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "MediaSessionManager.h"
 
+#include "Logging.h"
 #include "MediaSession.h"
 
 namespace WebCore {
@@ -39,7 +40,7 @@ MediaSessionManager& MediaSessionManager::sharedManager()
 #endif
 
 MediaSessionManager::MediaSessionManager()
-    : m_interruptions(0)
+    : m_interrupted(false)
 {
     resetRestrictions();
 }
@@ -78,19 +79,18 @@ int MediaSessionManager::count(MediaSession::MediaType type) const
 
 void MediaSessionManager::beginInterruption()
 {
-    if (++m_interruptions > 1)
-        return;
+    LOG(Media, "MediaSessionManager::beginInterruption");
 
+    m_interrupted = true;
     for (auto* session : m_sessions)
         session->beginInterruption();
 }
 
 void MediaSessionManager::endInterruption(MediaSession::EndInterruptionFlags flags)
 {
-    ASSERT(m_interruptions > 0);
-    if (--m_interruptions)
-        return;
-    
+    LOG(Media, "MediaSessionManager::endInterruption");
+
+    m_interrupted = false;
     for (auto* session : m_sessions)
         session->endInterruption(flags);
 }
@@ -98,7 +98,8 @@ void MediaSessionManager::endInterruption(MediaSession::EndInterruptionFlags fla
 void MediaSessionManager::addSession(MediaSession& session)
 {
     m_sessions.append(&session);
-    session.setState(m_interruptions ? MediaSession::Interrupted : MediaSession::Running);
+    if (m_interrupted)
+        session.setState(MediaSession::Interrupted);
     updateSessionState();
 }
 
@@ -157,6 +158,24 @@ bool MediaSessionManager::sessionRestrictsInlineVideoPlayback(const MediaSession
     return m_restrictions[sessionType] & InlineVideoPlaybackRestricted;
 }
 
+void MediaSessionManager::applicationWillEnterBackground() const
+{
+    LOG(Media, "MediaSessionManager::applicationWillEnterBackground");
+    for (auto* session : m_sessions) {
+        if (m_restrictions[session->mediaType()] & BackgroundPlaybackNotPermitted)
+            session->beginInterruption();
+    }
+}
+
+void MediaSessionManager::applicationWillEnterForeground() const
+{
+    LOG(Media, "MediaSessionManager::applicationWillEnterForeground");
+    for (auto* session : m_sessions) {
+        if (m_restrictions[session->mediaType()] & BackgroundPlaybackNotPermitted)
+            session->endInterruption(MediaSession::MayResumePlaying);
+    }
+}
+
 #if !PLATFORM(MAC)
 void MediaSessionManager::updateSessionState()
 {
index 227bf1b..cb37d74 100644 (file)
@@ -47,12 +47,16 @@ public:
     void beginInterruption();
     void endInterruption(MediaSession::EndInterruptionFlags);
 
+    void applicationWillEnterForeground() const;
+    void applicationWillEnterBackground() const;
+
     enum SessionRestrictionFlags {
         NoRestrictions = 0,
         ConcurrentPlaybackNotPermitted = 1 << 0,
         InlineVideoPlaybackRestricted = 1 << 1,
         MetadataPreloadingNotPermitted = 1 << 2,
         AutoPreloadingNotPermitted = 1 << 3,
+        BackgroundPlaybackNotPermitted = 1 << 4,
     };
     typedef unsigned SessionRestrictions;
     
@@ -62,6 +66,7 @@ public:
     virtual void resetRestrictions();
 
     void sessionWillBeginPlayback(const MediaSession&) const;
+
     bool sessionRestrictsInlineVideoPlayback(const MediaSession&) const;
 
 protected:
@@ -77,7 +82,7 @@ private:
     SessionRestrictions m_restrictions[MediaSession::WebAudio + 1];
 
     Vector<MediaSession*> m_sessions;
-    int m_interruptions;
+    bool m_interrupted;
 };
 
 }
index b3ac71d..b4a0022 100644 (file)
@@ -37,7 +37,7 @@ namespace WebCore {
 
 class MediaSessionManageriOS : public MediaSessionManager {
 public:
-    virtual ~MediaSessionManageriOS() { }
+    virtual ~MediaSessionManageriOS();
 
 private:
     friend class MediaSessionManager;
index dfe6606..681c714 100644 (file)
 #import "Logging.h"
 #import "MediaSession.h"
 #import "SoftLinking.h"
-#import "WebCoreThreadRun.h"
 #import "WebCoreSystemInterface.h"
+#import "WebCoreThreadRun.h"
 #import <AVFoundation/AVAudioSession.h>
+#import <UIKit/UIApplication.h>
 #import <objc/runtime.h>
 #import <wtf/RetainPtr.h>
 
 SOFT_LINK_FRAMEWORK(AVFoundation)
+SOFT_LINK_FRAMEWORK(UIKit)
 
 SOFT_LINK_CLASS(AVFoundation, AVAudioSession)
 
 SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionNotification, NSString *)
 SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionTypeKey, NSString *)
 SOFT_LINK_POINTER(AVFoundation, AVAudioSessionInterruptionOptionKey, NSString *)
+SOFT_LINK_POINTER(UIKit, UIApplicationWillResignActiveNotification, NSString *)
+SOFT_LINK_POINTER(UIKit, UIApplicationWillEnterForegroundNotification, NSString *)
 
 #define AVAudioSession getAVAudioSessionClass()
 #define AVAudioSessionInterruptionNotification getAVAudioSessionInterruptionNotification()
 #define AVAudioSessionInterruptionTypeKey getAVAudioSessionInterruptionTypeKey()
 #define AVAudioSessionInterruptionOptionKey getAVAudioSessionInterruptionOptionKey()
+#define UIApplicationWillResignActiveNotification getUIApplicationWillResignActiveNotification()
+#define UIApplicationWillEnterForegroundNotification getUIApplicationWillEnterForegroundNotification()
 
 using namespace WebCore;
 
@@ -57,10 +63,12 @@ using namespace WebCore;
 }
 
 - (id)initWithCallback:(MediaSessionManageriOS*)callback;
+- (void)clearCallback;
 - (void)interruption:(NSNotification*)notification;
+- (void)applicationWillEnterForeground:(NSNotification*)notification;
+- (void)applicationWillResignActive:(NSNotification*)notification;
 @end
 
-
 namespace WebCore {
 
 MediaSessionManager& MediaSessionManager::sharedManager()
@@ -76,6 +84,11 @@ MediaSessionManageriOS::MediaSessionManageriOS()
     resetRestrictions();
 }
 
+MediaSessionManageriOS::~MediaSessionManageriOS()
+{
+    [m_objcObserver clearCallback];
+}
+
 void MediaSessionManageriOS::resetRestrictions()
 {
     MediaSessionManager::resetRestrictions();
@@ -85,6 +98,8 @@ void MediaSessionManageriOS::resetRestrictions()
         addRestriction(MediaSession::Video, InlineVideoPlaybackRestricted);
 
     addRestriction(MediaSession::Video, ConcurrentPlaybackNotPermitted);
+    addRestriction(MediaSession::Video, BackgroundPlaybackNotPermitted);
+
     removeRestriction(MediaSession::Audio, MetadataPreloadingNotPermitted);
     removeRestriction(MediaSession::Video, MetadataPreloadingNotPermitted);
     addRestriction(MediaSession::Audio, AutoPreloadingNotPermitted);
@@ -104,7 +119,11 @@ void MediaSessionManageriOS::resetRestrictions()
 
     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
     [center addObserver:self selector:@selector(interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]];
-    
+
+    // FIXME: These need to be piped through from the UI process in WK2 mode.
+    [center addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
+    [center addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
+
     return self;
 }
 
@@ -114,6 +133,11 @@ void MediaSessionManageriOS::resetRestrictions()
     [super dealloc];
 }
 
+- (void)clearCallback
+{
+    _callback = nil;
+}
+
 - (void)interruption:(NSNotification *)notification
 {
     if (!_callback)
@@ -137,6 +161,36 @@ void MediaSessionManageriOS::resetRestrictions()
     });
 }
 
+- (void)applicationWillEnterForeground:(NSNotification *)notification
+{
+    UNUSED_PARAM(notification);
+    
+    if (!_callback)
+        return;
+
+    WebThreadRun(^{
+        if (!_callback)
+            return;
+        
+        _callback->applicationWillEnterForeground();
+    });
+}
+
+- (void)applicationWillResignActive:(NSNotification *)notification
+{
+    UNUSED_PARAM(notification);
+
+    if (!_callback)
+        return;
+    
+    WebThreadRun(^{
+        if (!_callback)
+            return;
+        
+        _callback->applicationWillEnterBackground();
+    });
+}
+
 @end
 
 #endif // PLATFORM(IOS)
index 8cfe4eb..f35c2a4 100644 (file)
@@ -47,7 +47,9 @@ public:
     virtual void start() override;
     virtual void stop() override;
     virtual bool isPlaying() override { return m_isPlaying; }
+
     virtual void pausePlayback() override { stop(); }
+    virtual void resumePlayback() override { start(); }
 
     virtual float sampleRate() const override { return m_sampleRate; }
 
index 7d8efc5..b1cb517 100644 (file)
@@ -2188,6 +2188,16 @@ void Internals::endMediaSessionInterruption(const String& flagsString)
     MediaSessionManager::sharedManager().endInterruption(flags);
 }
 
+void Internals::applicationWillEnterForeground() const
+{
+    MediaSessionManager::sharedManager().applicationWillEnterForeground();
+}
+
+void Internals::applicationWillEnterBackground() const
+{
+    MediaSessionManager::sharedManager().applicationWillEnterBackground();
+}
+
 void Internals::setMediaSessionRestrictions(const String& mediaTypeString, const String& restrictionsString, ExceptionCode& ec)
 {
     MediaSession::MediaType mediaType = MediaSession::None;
@@ -2215,6 +2225,8 @@ void Internals::setMediaSessionRestrictions(const String& mediaTypeString, const
         restrictions += MediaSessionManager::MetadataPreloadingNotPermitted;
     if (equalIgnoringCase(restrictionsString, "AutoPreloadingNotPermitted"))
         restrictions += MediaSessionManager::AutoPreloadingNotPermitted;
+    if (equalIgnoringCase(restrictionsString, "BackgroundPlaybackNotPermitted"))
+        restrictions += MediaSessionManager::BackgroundPlaybackNotPermitted;
 
     MediaSessionManager::sharedManager().addRestriction(mediaType, restrictions);
 }
index 3ac89f4..185f850 100644 (file)
@@ -326,6 +326,8 @@ public:
 
     void beginMediaSessionInterruption();
     void endMediaSessionInterruption(const String&);
+    void applicationWillEnterForeground() const;
+    void applicationWillEnterBackground() const;
     void setMediaSessionRestrictions(const String& mediaType, const String& restrictions, ExceptionCode& ec);
 
 private:
index 5fadd38..35158c0 100644 (file)
 
     void beginMediaSessionInterruption();
     void endMediaSessionInterruption(DOMString flags);
+    void applicationWillEnterForeground();
+    void applicationWillEnterBackground();
     [RaisesException] void setMediaSessionRestrictions(DOMString mediaType, DOMString restrictions);
 };
index 96c289b..2501736 100644 (file)
@@ -1,3 +1,13 @@
+2014-02-04  Eric Carlson  <eric.carlson@apple.com>
+
+        Refine MediaSession interruptions
+        https://bugs.webkit.org/show_bug.cgi?id=128125
+
+        Reviewed by Jer Noble.
+
+        * WebKit.vcxproj/WebKitExportGenerator/WebKitExports.def.in: Export applicationWillEnterForeground
+            and applicationWillEnterBackground for Internals.
+
 2014-01-31  Oliver Hunt  <oliver@apple.com>
 
         Rollout r163195 and related patches
index fad2938..0892a93 100644 (file)
@@ -250,6 +250,8 @@ EXPORTS
         symbolWithPointer(?addRestriction@MediaSessionManager@WebCore@@QAEXW4MediaType@MediaSession@2@I@Z, ?addRestriction@MediaSessionManager@WebCore@@QEAAXW4MediaType@MediaSession@2@I@Z)
         symbolWithPointer(?removeRestriction@MediaSessionManager@WebCore@@QAEXW4MediaType@MediaSession@2@I@Z, ?removeRestriction@MediaSessionManager@WebCore@@QEAAXW4MediaType@MediaSession@2@I@Z)
         symbolWithPointer(?restrictions@MediaSessionManager@WebCore@@QAEIW4MediaType@MediaSession@2@@Z, ?restrictions@MediaSessionManager@WebCore@@QEAAIW4MediaType@MediaSession@2@@Z)
+        symbolWithPointer(?applicationWillEnterForeground@MediaSessionManager@WebCore@@QBEXXZ, ?applicationWillEnterForeground@MediaSessionManager@WebCore@@QBEXXZ)
+        symbolWithPointer(?applicationWillEnterBackground@MediaSessionManager@WebCore@@QBEXXZ, ?applicationWillEnterBackground@MediaSessionManager@WebCore@@QBEXXZ)
         ?localUserSpecificStorageDirectory@WebCore@@YA?AVString@WTF@@XZ
         symbolWithPointer(?namedItem@StaticNodeList@WebCore@@UBEPAVNode@2@ABVAtomicString@WTF@@@Z, ?namedItem@StaticNodeList@WebCore@@UEBAPEAVNode@2@AEBVAtomicString@WTF@@@Z)
         ?number@String@WTF@@SA?AV12@_J@Z