[iOS] Sync media playback with now playing
authoreric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 13 Mar 2014 18:24:06 +0000 (18:24 +0000)
committereric.carlson@apple.com <eric.carlson@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 13 Mar 2014 18:24:06 +0000 (18:24 +0000)
https://bugs.webkit.org/show_bug.cgi?id=130172

Reviewed by Jer Noble.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::play): Move clientWillBeginPlayback to
    playInternal so it is called every time playback begins.
(WebCore::HTMLMediaElement::playInternal): Call clientWillBeginPlayback.
(WebCore::HTMLMediaElement::pause): Move clientWillPausePlayback to
    playInternal so it is called every time playback begins.
(WebCore::HTMLMediaElement::playInternal): Call clientWillPausePlayback.
(WebCore::HTMLMediaElement::mediaSessionTitle): New. Return the 'title' attribute,
    or currenSrc if that is empty.
* html/HTMLMediaElement.h:
(WebCore::HTMLMediaElement::mediaSessionDuration): Return duration.
(WebCore::HTMLMediaElement::mediaSessionCurrentTime): Return current time.

* platform/audio/MediaSession.cpp:
(WebCore::MediaSession::clientWillPausePlayback): New, passthrough to the
    media element.
(WebCore::MediaSession::title): Ditto.
(WebCore::MediaSession::duration): Ditto.
(WebCore::MediaSession::currentTime): Ditto.
* platform/audio/MediaSession.h:
(WebCore::MediaSessionClient::mediaSessionTitle):
(WebCore::MediaSessionClient::mediaSessionDuration):
(WebCore::MediaSessionClient::mediaSessionCurrentTime):

* platform/audio/MediaSessionManager.cpp:
(WebCore::MediaSessionManager::MediaSessionManager): Initialize m_activeSession.
(WebCore::MediaSessionManager::removeSession): Set m_activeSession if the session
    being removed is currently active.
(WebCore::MediaSessionManager::sessionWillBeginPlayback): Set m_activeSession.
* platform/audio/MediaSessionManager.h:
(WebCore::MediaSessionManager::sessionWillEndPlayback):
(WebCore::MediaSessionManager::setCurrentSession):
(WebCore::MediaSessionManager::currentSession):

* platform/audio/ios/MediaSessionManagerIOS.h:
* platform/audio/ios/MediaSessionManagerIOS.mm:
(WebCore::MediaSessionManageriOS::sessionWillBeginPlayback): Call updateNowPlayingInfo.
(WebCore::MediaSessionManageriOS::sessionWillEndPlayback): Ditto.
(WebCore::MediaSessionManageriOS::updateNowPlayingInfo): Update MPNowPlayingInfoCenter
    with the current media item's title, duration, and current time.
(-[WebMediaSessionHelper initWithCallback:]): Turn on deliver of remote control
    events, even though we don't respond to them yet, or Now Playing won't work.

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

Source/WebCore/ChangeLog
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLMediaElement.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

index b0c38f5..238ba73 100644 (file)
@@ -1,3 +1,53 @@
+2014-03-12  Eric Carlson  <eric.carlson@apple.com>
+
+        [iOS] Sync media playback with now playing
+        https://bugs.webkit.org/show_bug.cgi?id=130172
+
+        Reviewed by Jer Noble.
+        
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::play): Move clientWillBeginPlayback to
+            playInternal so it is called every time playback begins.
+        (WebCore::HTMLMediaElement::playInternal): Call clientWillBeginPlayback.
+        (WebCore::HTMLMediaElement::pause): Move clientWillPausePlayback to
+            playInternal so it is called every time playback begins.
+        (WebCore::HTMLMediaElement::playInternal): Call clientWillPausePlayback.
+        (WebCore::HTMLMediaElement::mediaSessionTitle): New. Return the 'title' attribute,
+            or currenSrc if that is empty.
+        * html/HTMLMediaElement.h:
+        (WebCore::HTMLMediaElement::mediaSessionDuration): Return duration.
+        (WebCore::HTMLMediaElement::mediaSessionCurrentTime): Return current time.
+
+        * platform/audio/MediaSession.cpp:
+        (WebCore::MediaSession::clientWillPausePlayback): New, passthrough to the
+            media element.
+        (WebCore::MediaSession::title): Ditto.
+        (WebCore::MediaSession::duration): Ditto.
+        (WebCore::MediaSession::currentTime): Ditto.
+        * platform/audio/MediaSession.h:
+        (WebCore::MediaSessionClient::mediaSessionTitle):
+        (WebCore::MediaSessionClient::mediaSessionDuration):
+        (WebCore::MediaSessionClient::mediaSessionCurrentTime):
+
+        * platform/audio/MediaSessionManager.cpp:
+        (WebCore::MediaSessionManager::MediaSessionManager): Initialize m_activeSession.
+        (WebCore::MediaSessionManager::removeSession): Set m_activeSession if the session
+            being removed is currently active.
+        (WebCore::MediaSessionManager::sessionWillBeginPlayback): Set m_activeSession.
+        * platform/audio/MediaSessionManager.h:
+        (WebCore::MediaSessionManager::sessionWillEndPlayback):
+        (WebCore::MediaSessionManager::setCurrentSession):
+        (WebCore::MediaSessionManager::currentSession):
+
+        * platform/audio/ios/MediaSessionManagerIOS.h:
+        * platform/audio/ios/MediaSessionManagerIOS.mm:
+        (WebCore::MediaSessionManageriOS::sessionWillBeginPlayback): Call updateNowPlayingInfo. 
+        (WebCore::MediaSessionManageriOS::sessionWillEndPlayback): Ditto.
+        (WebCore::MediaSessionManageriOS::updateNowPlayingInfo): Update MPNowPlayingInfoCenter
+            with the current media item's title, duration, and current time.
+        (-[WebMediaSessionHelper initWithCallback:]): Turn on deliver of remote control
+            events, even though we don't respond to them yet, or Now Playing won't work.
+
 2014-03-13  Radu Stavila  <stavila@adobe.com>
 
         Webkit not building on XCode 5.1 due to garbage collection no longer being supported
index 61b6f8a..b7f5eaa 100644 (file)
@@ -2684,18 +2684,18 @@ void HTMLMediaElement::play()
     if (ScriptController::processingUserGesture())
         removeBehaviorsRestrictionsAfterFirstUserGesture();
 
-    if (!m_mediaSession->clientWillBeginPlayback()) {
-        LOG(Media, "  returning because of interruption");
-        return;
-    }
-    
     playInternal();
 }
 
 void HTMLMediaElement::playInternal()
 {
     LOG(Media, "HTMLMediaElement::playInternal");
-
+    
+    if (!m_mediaSession->clientWillBeginPlayback()) {
+        LOG(Media, "  returning because of interruption");
+        return;
+    }
+    
     // 4.8.10.9. Playing the media resource
     if (!m_player || m_networkState == NETWORK_EMPTY)
         scheduleDelayedAction(LoadMediaResource);
@@ -2731,11 +2731,6 @@ void HTMLMediaElement::pause()
     if (!m_mediaSession->playbackPermitted(*this))
         return;
 
-    if (!m_mediaSession->clientWillPausePlayback()) {
-        LOG(Media, "  returning because of interruption");
-        return;
-    }
-
     pauseInternal();
 }
 
@@ -2744,6 +2739,11 @@ void HTMLMediaElement::pauseInternal()
 {
     LOG(Media, "HTMLMediaElement::pauseInternal");
 
+    if (!m_mediaSession->clientWillPausePlayback()) {
+        LOG(Media, "  returning because of interruption");
+        return;
+    }
+    
     // 4.8.10.9. Playing the media resource
     if (!m_player || m_networkState == NETWORK_EMPTY) {
 #if PLATFORM(IOS)
@@ -5878,6 +5878,14 @@ void HTMLMediaElement::resumePlayback()
     if (paused())
         play();
 }
+    
+String HTMLMediaElement::mediaSessionTitle() const
+{
+    if (fastHasAttribute(titleAttr))
+        return fastGetAttribute(titleAttr);
+    
+    return m_currentSrc;
+}
 
 }
 
index c99ef95..214c40b 100644 (file)
@@ -690,9 +690,13 @@ private:
     bool ensureMediaControlsInjectedScript();
 #endif
 
+    // MediaSessionClient Overrides
     virtual MediaSession::MediaType mediaType() const override;
     virtual void pausePlayback() override;
     virtual void resumePlayback() override;
+    virtual String mediaSessionTitle() const override;
+    virtual double mediaSessionDuration() const override { return duration(); }
+    virtual double mediaSessionCurrentTime() const override { return currentTime(); }
 
     Timer<HTMLMediaElement> m_loadTimer;
     Timer<HTMLMediaElement> m_progressEventTimer;
index b2b2593..3bea1e0 100644 (file)
@@ -115,6 +115,7 @@ bool MediaSession::clientWillPausePlayback()
     }
     
     setState(Paused);
+    MediaSessionManager::sharedManager().sessionWillEndPlayback(*this);
     return true;
 }
 
@@ -128,5 +129,20 @@ MediaSession::MediaType MediaSession::mediaType() const
 {
     return m_client.mediaType();
 }
+
+String MediaSession::title() const
+{
+    return m_client.mediaSessionTitle();
+}
+
+double MediaSession::duration() const
+{
+    return m_client.mediaSessionDuration();
+}
+
+double MediaSession::currentTime() const
+{
+    return m_client.mediaSessionCurrentTime();
+}
     
 }
index 779c7a0..d581b7c 100644 (file)
@@ -26,6 +26,7 @@
 #ifndef MediaSession_h
 #define MediaSession_h
 
+#include "MediaPlayer.h"
 #include <wtf/Noncopyable.h>
 
 namespace WebCore {
@@ -70,6 +71,10 @@ public:
     bool clientWillPausePlayback();
 
     void pauseSession();
+    
+    String title() const;
+    double duration() const;
+    double currentTime() const;
 
 protected:
     MediaSessionClient& client() const { return m_client; }
@@ -89,7 +94,11 @@ public:
     virtual MediaSession::MediaType mediaType() const = 0;
     virtual void resumePlayback() = 0;
     virtual void pausePlayback() = 0;
-
+    
+    virtual String mediaSessionTitle() const { return String(); }
+    virtual double mediaSessionDuration() const { return MediaPlayer::invalidTime(); }
+    virtual double mediaSessionCurrentTime() const { return MediaPlayer::invalidTime(); }
+    
 protected:
     virtual ~MediaSessionClient() { }
 };
index 0dbf3f0..3b05e68 100644 (file)
@@ -40,7 +40,8 @@ MediaSessionManager& MediaSessionManager::sharedManager()
 #endif
 
 MediaSessionManager::MediaSessionManager()
-    : m_interrupted(false)
+    : m_activeSession(nullptr)
+    , m_interrupted(false)
 {
     resetRestrictions();
 }
@@ -109,7 +110,10 @@ void MediaSessionManager::removeSession(MediaSession& session)
     ASSERT(index != notFound);
     if (index == notFound)
         return;
-
+    
+    if (m_activeSession == &session)
+        setCurrentSession(nullptr);
+    
     m_sessions.remove(index);
     updateSessionState();
 }
@@ -132,8 +136,10 @@ MediaSessionManager::SessionRestrictions MediaSessionManager::restrictions(Media
     return m_restrictions[type];
 }
 
-void MediaSessionManager::sessionWillBeginPlayback(const MediaSession& session) const
+void MediaSessionManager::sessionWillBeginPlayback(const MediaSession& session)
 {
+    setCurrentSession(&session);
+    
     MediaSession::MediaType sessionType = session.mediaType();
     SessionRestrictions restrictions = m_restrictions[sessionType];
     if (!restrictions & ConcurrentPlaybackNotPermitted)
index f668c9e..60b1579 100644 (file)
@@ -65,8 +65,9 @@ public:
     SessionRestrictions restrictions(MediaSession::MediaType);
     virtual void resetRestrictions();
 
-    void sessionWillBeginPlayback(const MediaSession&) const;
-
+    virtual void sessionWillBeginPlayback(const MediaSession&);
+    virtual void sessionWillEndPlayback(const MediaSession&) { }
+    
     bool sessionRestrictsInlineVideoPlayback(const MediaSession&) const;
 
 #if ENABLE(IOS_AIRPLAY)
@@ -79,13 +80,17 @@ protected:
 
     void addSession(MediaSession&);
     void removeSession(MediaSession&);
-
+    
+    void setCurrentSession(const MediaSession* session) { m_activeSession = session; }
+    const MediaSession* currentSession() { return m_activeSession; }
+    
 private:
     void updateSessionState();
 
     SessionRestrictions m_restrictions[MediaSession::WebAudio + 1];
 
     Vector<MediaSession*> m_sessions;
+    const MediaSession* m_activeSession;
     bool m_interrupted;
 };
 
index a107e39..ebd565a 100644 (file)
@@ -48,6 +48,11 @@ public:
 private:
     friend class MediaSessionManager;
 
+    virtual void sessionWillBeginPlayback(const MediaSession&) override;
+    virtual void sessionWillEndPlayback(const MediaSession&) override;
+    
+    void updateNowPlayingInfo();
+    
     virtual void resetRestrictions() override;
 
 #if ENABLE(IOS_AIRPLAY)
index eaf976e..279f06d 100644 (file)
 #import "WebCoreSystemInterface.h"
 #import "WebCoreThreadRun.h"
 #import <AVFoundation/AVAudioSession.h>
+#import <MediaPlayer/MPMediaItem.h>
+#import <MediaPlayer/MPNowPlayingInfoCenter.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 *)
-SOFT_LINK_POINTER(UIKit, UIApplicationDidBecomeActiveNotification, NSString *)
 
 #define AVAudioSession getAVAudioSessionClass()
 #define AVAudioSessionInterruptionNotification getAVAudioSessionInterruptionNotification()
 #define AVAudioSessionInterruptionTypeKey getAVAudioSessionInterruptionTypeKey()
 #define AVAudioSessionInterruptionOptionKey getAVAudioSessionInterruptionOptionKey()
+
+SOFT_LINK_FRAMEWORK(UIKit)
+SOFT_LINK_CLASS(UIKit, UIApplication)
+SOFT_LINK_POINTER(UIKit, UIApplicationWillResignActiveNotification, NSString *)
+SOFT_LINK_POINTER(UIKit, UIApplicationWillEnterForegroundNotification, NSString *)
+SOFT_LINK_POINTER(UIKit, UIApplicationDidBecomeActiveNotification, NSString *)
+
+#define UIApplication getUIApplicationClass()
 #define UIApplicationWillResignActiveNotification getUIApplicationWillResignActiveNotification()
 #define UIApplicationWillEnterForegroundNotification getUIApplicationWillEnterForegroundNotification()
 #define UIApplicationDidBecomeActiveNotification getUIApplicationDidBecomeActiveNotification()
 
+SOFT_LINK_FRAMEWORK(MediaPlayer)
+SOFT_LINK_CLASS(MediaPlayer, MPNowPlayingInfoCenter)
+SOFT_LINK_POINTER(MediaPlayer, MPMediaItemPropertyTitle, NSString *)
+SOFT_LINK_POINTER(MediaPlayer, MPMediaItemPropertyPlaybackDuration, NSString *)
+SOFT_LINK_POINTER(MediaPlayer, MPNowPlayingInfoPropertyElapsedPlaybackTime, NSString *)
+SOFT_LINK_POINTER(MediaPlayer, MPNowPlayingInfoPropertyPlaybackRate, NSString *)
+
+#define MPMediaItemPropertyTitle getMPMediaItemPropertyTitle()
+#define MPMediaItemPropertyPlaybackDuration getMPMediaItemPropertyPlaybackDuration()
+#define MPNowPlayingInfoPropertyElapsedPlaybackTime getMPNowPlayingInfoPropertyElapsedPlaybackTime()
+#define MPNowPlayingInfoPropertyPlaybackRate getMPNowPlayingInfoPropertyPlaybackRate()
+
 NSString* WebUIApplicationWillResignActiveNotification = @"WebUIApplicationWillResignActiveNotification";
 NSString* WebUIApplicationWillEnterForegroundNotification = @"WebUIApplicationWillEnterForegroundNotification";
 NSString* WebUIApplicationDidBecomeActiveNotification = @"WebUIApplicationDidBecomeActiveNotification";
@@ -119,6 +135,46 @@ void MediaSessionManageriOS::showPlaybackTargetPicker()
     notImplemented();
 }
 #endif
+    
+void MediaSessionManageriOS::sessionWillBeginPlayback(const MediaSession& session)
+{
+    MediaSessionManager::sessionWillBeginPlayback(session);
+    updateNowPlayingInfo();
+}
+    
+void MediaSessionManageriOS::sessionWillEndPlayback(const MediaSession& session)
+{
+    MediaSessionManager::sessionWillEndPlayback(session);
+    updateNowPlayingInfo();
+}
+    
+void MediaSessionManageriOS::updateNowPlayingInfo()
+{
+    MPNowPlayingInfoCenter *nowPlaying = (MPNowPlayingInfoCenter *)[getMPNowPlayingInfoCenterClass() defaultCenter];
+    const MediaSession* currentSession = this->currentSession();
+    
+    if (!currentSession) {
+        [nowPlaying setNowPlayingInfo:nil];
+        return;
+    }
+    
+    RetainPtr<NSMutableDictionary> info = adoptNS([[NSMutableDictionary alloc] init]);
+    
+    String title = currentSession->title();
+    if (!title.isEmpty())
+        [info setValue:static_cast<NSString *>(title) forKey:MPMediaItemPropertyTitle];
+    
+    double duration = currentSession->duration();
+    if (std::isfinite(duration) && duration != MediaPlayer::invalidTime())
+        [info setValue:@(duration) forKey:MPMediaItemPropertyPlaybackDuration];
+    
+    double currentTime = currentSession->currentTime();
+    if (std::isfinite(currentTime))
+        [info setValue:@(currentTime) forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
+    
+    [info setValue:(currentSession->state() == MediaSession::Playing ? @YES : @NO) forKey:MPNowPlayingInfoPropertyPlaybackRate];
+    [nowPlaying setNowPlayingInfo:info.get()];
+}
 
 } // namespace WebCore
 
@@ -134,14 +190,16 @@ void MediaSessionManageriOS::showPlaybackTargetPicker()
     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(applicationWillEnterForeground:) name:WebUIApplicationWillEnterForegroundNotification object:nil];
     [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
     [center addObserver:self selector:@selector(applicationDidBecomeActive:) name:WebUIApplicationDidBecomeActiveNotification object:nil];
     [center addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
     [center addObserver:self selector:@selector(applicationWillResignActive:) name:WebUIApplicationWillResignActiveNotification object:nil];
-
+    
+    // Now playing won't work unless we turn on the delivery of remote control events.
+    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
+    
     return self;
 }