Video playback in Safari should continue when CarPlay is plugged in
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 30 May 2019 16:29:38 +0000 (16:29 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 30 May 2019 16:29:38 +0000 (16:29 +0000)
https://bugs.webkit.org/show_bug.cgi?id=198345
<rdar://problem/45505750>

Reviewed by Eric Carlson.

Source/WebCore:

Test: media/video-isplayingtoautomotiveheadunit.html

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction const):
* platform/audio/PlatformMediaSessionManager.cpp:
(WebCore::PlatformMediaSessionManager::setIsPlayingToAutomotiveHeadUnit):
* platform/audio/PlatformMediaSessionManager.h:
(WebCore::PlatformMediaSessionManager::isPlayingToAutomotiveHeadUnit const):
* platform/audio/ios/MediaSessionManagerIOS.h:
* platform/audio/ios/MediaSessionManagerIOS.mm:
(WebCore::MediaSessionManageriOS::MediaSessionManageriOS):
(WebCore::MediaSessionManageriOS::carPlayServerDied):
(WebCore::MediaSessionManageriOS::updateCarPlayIsConnected):
(-[WebMediaSessionHelper initWithCallback:]):
(-[WebMediaSessionHelper startMonitoringAirPlayRoutes]):
(-[WebMediaSessionHelper interruption:]):
(-[WebMediaSessionHelper applicationWillEnterForeground:]):
(-[WebMediaSessionHelper applicationDidBecomeActive:]):
(-[WebMediaSessionHelper applicationWillResignActive:]):
(-[WebMediaSessionHelper wirelessRoutesAvailableDidChange:]):
(-[WebMediaSessionHelper applicationDidEnterBackground:]):
(-[WebMediaSessionHelper carPlayServerDied:]):
(-[WebMediaSessionHelper carPlayIsConnectedDidChange:]):
* testing/Internals.cpp:
(WebCore::Internals::resetToConsistentState):
(WebCore::Internals::setIsPlayingToAutomotiveHeadUnit):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* media/video-isplayingtoautomotiveheadunit-expected.txt: Added.
* media/video-isplayingtoautomotiveheadunit.html: Added.

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

12 files changed:
LayoutTests/ChangeLog
LayoutTests/media/video-isplayingtoautomotiveheadunit-expected.txt [new file with mode: 0644]
LayoutTests/media/video-isplayingtoautomotiveheadunit.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/platform/audio/PlatformMediaSessionManager.cpp
Source/WebCore/platform/audio/PlatformMediaSessionManager.h
Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.h
Source/WebCore/platform/audio/ios/MediaSessionManagerIOS.mm
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl

index e7da17f..39889c2 100644 (file)
@@ -1,3 +1,14 @@
+2019-05-30  Jer Noble  <jer.noble@apple.com>
+
+        Video playback in Safari should continue when CarPlay is plugged in
+        https://bugs.webkit.org/show_bug.cgi?id=198345
+        <rdar://problem/45505750>
+
+        Reviewed by Eric Carlson.
+
+        * media/video-isplayingtoautomotiveheadunit-expected.txt: Added.
+        * media/video-isplayingtoautomotiveheadunit.html: Added.
+
 2019-05-29  Said Abou-Hallawa  <sabouhallawa@apple.com>
 
         REGRESSION (r244182) [Mac WK2] Layout Test imported/w3c/web-platform-tests/visual-viewport/viewport-resize-event-on-load-overflowing-page.html is a flaky failure
diff --git a/LayoutTests/media/video-isplayingtoautomotiveheadunit-expected.txt b/LayoutTests/media/video-isplayingtoautomotiveheadunit-expected.txt
new file mode 100644 (file)
index 0000000..063e5ed
--- /dev/null
@@ -0,0 +1,15 @@
+
+RUN(video.src = findMediaFile("video", "content/test"))
+EVENT(canplaythrough)
+RUN(video.play())
+EVENT(playing)
+RUN(internals.setMediaSessionRestrictions("videoaudio", "suspendedunderlockplaybackrestricted"))
+RUN(internals.applicationDidEnterBackground(true))
+EVENT(pause)
+RUN(internals.applicationWillEnterForeground(true))
+EVENT(playing)
+RUN(internals.setIsPlayingToAutomotiveHeadUnit(true))
+RUN(internals.applicationDidEnterBackground(true))
+EXPECTED (video.paused == 'false') OK
+END OF TEST
+
diff --git a/LayoutTests/media/video-isplayingtoautomotiveheadunit.html b/LayoutTests/media/video-isplayingtoautomotiveheadunit.html
new file mode 100644 (file)
index 0000000..b1053f9
--- /dev/null
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>video-isplayingtoautomotiveheadunit</title>
+    <script src="video-test.js"></script>
+    <script src="media-file.js"></script>
+    <script>
+    window.addEventListener('load', async event => {
+        findMediaElement();
+
+        run('video.src = findMediaFile("video", "content/test")');
+        await waitFor(video, 'canplaythrough');
+
+        runWithKeyDown('video.play()');
+        await waitFor(video, 'playing');
+
+        run('internals.setMediaSessionRestrictions("videoaudio", "suspendedunderlockplaybackrestricted")')
+        run('internals.applicationDidEnterBackground(true)');
+        await waitFor(video, 'pause');
+
+        run('internals.applicationWillEnterForeground(true)');
+        await waitFor(video, 'playing');
+
+        run('internals.setIsPlayingToAutomotiveHeadUnit(true)');
+        run('internals.applicationDidEnterBackground(true)');
+        testExpected('video.paused', false);
+        endTest();
+    });
+    </script>
+</head>
+<body>
+    <video controls></video>
+</body>
+</html>
\ No newline at end of file
index 0ef8ff4..b88afa9 100644 (file)
@@ -1,3 +1,40 @@
+2019-05-30  Jer Noble  <jer.noble@apple.com>
+
+        Video playback in Safari should continue when CarPlay is plugged in
+        https://bugs.webkit.org/show_bug.cgi?id=198345
+        <rdar://problem/45505750>
+
+        Reviewed by Eric Carlson.
+
+        Test: media/video-isplayingtoautomotiveheadunit.html
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction const):
+        * platform/audio/PlatformMediaSessionManager.cpp:
+        (WebCore::PlatformMediaSessionManager::setIsPlayingToAutomotiveHeadUnit):
+        * platform/audio/PlatformMediaSessionManager.h:
+        (WebCore::PlatformMediaSessionManager::isPlayingToAutomotiveHeadUnit const):
+        * platform/audio/ios/MediaSessionManagerIOS.h:
+        * platform/audio/ios/MediaSessionManagerIOS.mm:
+        (WebCore::MediaSessionManageriOS::MediaSessionManageriOS):
+        (WebCore::MediaSessionManageriOS::carPlayServerDied):
+        (WebCore::MediaSessionManageriOS::updateCarPlayIsConnected):
+        (-[WebMediaSessionHelper initWithCallback:]):
+        (-[WebMediaSessionHelper startMonitoringAirPlayRoutes]):
+        (-[WebMediaSessionHelper interruption:]):
+        (-[WebMediaSessionHelper applicationWillEnterForeground:]):
+        (-[WebMediaSessionHelper applicationDidBecomeActive:]):
+        (-[WebMediaSessionHelper applicationWillResignActive:]):
+        (-[WebMediaSessionHelper wirelessRoutesAvailableDidChange:]):
+        (-[WebMediaSessionHelper applicationDidEnterBackground:]):
+        (-[WebMediaSessionHelper carPlayServerDied:]):
+        (-[WebMediaSessionHelper carPlayIsConnectedDidChange:]):
+        * testing/Internals.cpp:
+        (WebCore::Internals::resetToConsistentState):
+        (WebCore::Internals::setIsPlayingToAutomotiveHeadUnit):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2019-05-29  Robin Morisset  <rmorisset@apple.com>
 
         [WHLSL] Parsing and lexing the standard library is slow
index f00d81c..75f1938 100644 (file)
@@ -7715,6 +7715,10 @@ bool HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction(PlatformMedia
             INFO_LOG(LOGIDENTIFIER, "returning true because isPlayingToExternalTarget() is true");
             return true;
         }
+        if (PlatformMediaSessionManager::sharedManager().isPlayingToAutomotiveHeadUnit()) {
+            INFO_LOG(LOGIDENTIFIER, "returning true because isPlayingToAutomotiveHeadUnit() is true");
+            return true;
+        }
         if (m_videoFullscreenMode & VideoFullscreenModePictureInPicture)
             return true;
 #if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
@@ -7726,6 +7730,10 @@ bool HTMLMediaElement::shouldOverrideBackgroundPlaybackRestriction(PlatformMedia
             INFO_LOG(LOGIDENTIFIER, "returning true because isPlayingToExternalTarget() is true");
             return true;
         }
+        if (PlatformMediaSessionManager::sharedManager().isPlayingToAutomotiveHeadUnit()) {
+            INFO_LOG(LOGIDENTIFIER, "returning true because isPlayingToAutomotiveHeadUnit() is true");
+            return true;
+        }
     }
     return false;
 }
index 8370fb0..b42c214 100644 (file)
@@ -391,6 +391,14 @@ void PlatformMediaSessionManager::processDidResume()
 #endif
 }
 
+void PlatformMediaSessionManager::setIsPlayingToAutomotiveHeadUnit(bool isPlayingToAutomotiveHeadUnit)
+{
+    if (isPlayingToAutomotiveHeadUnit == m_isPlayingToAutomotiveHeadUnit)
+        return;
+
+    ALWAYS_LOG(LOGIDENTIFIER, isPlayingToAutomotiveHeadUnit);
+    m_isPlayingToAutomotiveHeadUnit = isPlayingToAutomotiveHeadUnit;
+}
 
 void PlatformMediaSessionManager::sessionIsPlayingToWirelessPlaybackTargetChanged(PlatformMediaSession& session)
 {
index 3685307..b9119e0 100644 (file)
@@ -129,6 +129,9 @@ public:
 
     void sessionIsPlayingToWirelessPlaybackTargetChanged(PlatformMediaSession&);
 
+    WEBCORE_EXPORT void setIsPlayingToAutomotiveHeadUnit(bool);
+    bool isPlayingToAutomotiveHeadUnit() const { return m_isPlayingToAutomotiveHeadUnit; }
+
     void forEachMatchingSession(const Function<bool(const PlatformMediaSession&)>& predicate, const Function<void(PlatformMediaSession&)>& matchingCallback);
 
 protected:
@@ -188,6 +191,7 @@ private:
     mutable bool m_isApplicationInBackground { false };
     bool m_willIgnoreSystemInterruptions { false };
     bool m_processIsSuspended { false };
+    bool m_isPlayingToAutomotiveHeadUnit { false };
 
 #if USE(AUDIO_SESSION)
     bool m_becameActive { false };
index eb01d6f..479da7a 100644 (file)
@@ -47,6 +47,8 @@ public:
 
     void externalOutputDeviceAvailableDidChange();
     bool hasWirelessTargetsAvailable() override;
+    void carPlayServerDied();
+    void updateCarPlayIsConnected(Optional<bool>&&);
 
 private:
     friend class PlatformMediaSessionManager;
index 0043904..9d4eed6 100644 (file)
@@ -56,6 +56,10 @@ WEBCORE_EXPORT NSString* WebUIApplicationDidEnterBackgroundNotification = @"WebU
 SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(Celestial)
 SOFT_LINK_CLASS_OPTIONAL(Celestial, AVSystemController)
 SOFT_LINK_CONSTANT_MAY_FAIL(Celestial, AVSystemController_PIDToInheritApplicationStateFrom, NSString *)
+SOFT_LINK_CONSTANT_MAY_FAIL(Celestial, AVSystemController_CarPlayIsConnectedAttribute, NSString *)
+SOFT_LINK_CONSTANT_MAY_FAIL(Celestial, AVSystemController_CarPlayIsConnectedDidChangeNotification, NSString *)
+SOFT_LINK_CONSTANT_MAY_FAIL(Celestial, AVSystemController_CarPlayIsConnectedNotificationParameter, NSString *)
+SOFT_LINK_CONSTANT_MAY_FAIL(Celestial, AVSystemController_ServerConnectionDiedNotification, NSString *)
 #endif
 
 using namespace WebCore;
@@ -109,6 +113,8 @@ MediaSessionManageriOS::MediaSessionManageriOS()
     m_objcObserver = adoptNS([[WebMediaSessionHelper alloc] initWithCallback:this]);
     END_BLOCK_OBJC_EXCEPTIONS
     resetRestrictions();
+
+    updateCarPlayIsConnected(WTF::nullopt);
 }
 
 MediaSessionManageriOS::~MediaSessionManageriOS()
@@ -194,6 +200,27 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
     END_BLOCK_OBJC_EXCEPTIONS
 }
 
+void MediaSessionManageriOS::carPlayServerDied()
+{
+    ALWAYS_LOG(LOGIDENTIFIER);
+    updateCarPlayIsConnected(WTF::nullopt);
+}
+
+void MediaSessionManageriOS::updateCarPlayIsConnected(Optional<bool>&& carPlayIsConnected)
+{
+    if (carPlayIsConnected) {
+        setIsPlayingToAutomotiveHeadUnit(carPlayIsConnected.value());
+        return;
+    }
+
+    if (!canLoadAVSystemController_CarPlayIsConnectedAttribute()) {
+        setIsPlayingToAutomotiveHeadUnit(false)
+        return;
+    }
+
+    setIsPlayingToAutomotiveHeadUnit([[[getAVSystemControllerClass() sharedAVSystemController] attributeForKey:getAVSystemController_CarPlayIsConnectedAttribute()] boolValue]);
+}
+
 } // namespace WebCore
 
 @implementation WebMediaSessionHelper
@@ -218,6 +245,10 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
     [center addObserver:self selector:@selector(applicationWillResignActive:) name:WebUIApplicationWillResignActiveNotification object:nil];
     [center addObserver:self selector:@selector(applicationDidEnterBackground:) name:PAL::get_UIKit_UIApplicationDidEnterBackgroundNotification() object:nil];
     [center addObserver:self selector:@selector(applicationDidEnterBackground:) name:WebUIApplicationDidEnterBackgroundNotification object:nil];
+    if (canLoadAVSystemController_ServerConnectionDiedNotification())
+        [center addObserver:self selector:@selector(carPlayServerDied:) name:getAVSystemController_ServerConnectionDiedNotification() object:nil];
+    if (canLoadAVSystemController_CarPlayIsConnectedDidChangeNotification())
+        [center addObserver:self selector:@selector(carPlayIsConnectedDidChange:) name:getAVSystemController_CarPlayIsConnectedDidChangeNotification() object:nil];
 
     // Now playing won't work unless we turn on the delivery of remote control events.
     dispatch_async(dispatch_get_main_queue(), ^ {
@@ -286,14 +317,14 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
 
     LOG(Media, "-[WebMediaSessionHelper startMonitoringAirPlayRoutes]");
 
-    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self)]() mutable {
         ASSERT(!protectedSelf->_routeDetector);
 
         if (protectedSelf->_callback) {
             BEGIN_BLOCK_OBJC_EXCEPTIONS
             protectedSelf->_routeDetector = adoptNS([PAL::allocAVRouteDetectorInstance() init]);
             protectedSelf->_routeDetector.get().routeDetectionEnabled = protectedSelf->_monitoringAirPlayRoutes;
-            [[NSNotificationCenter defaultCenter] addObserver:protectedSelf selector:@selector(wirelessRoutesAvailableDidChange:) name:AVRouteDetectorMultipleRoutesDetectedDidChangeNotification object:protectedSelf->_routeDetector.get()];
+            [[NSNotificationCenter defaultCenter] addObserver:protectedSelf.get() selector:@selector(wirelessRoutesAvailableDidChange:) name:AVRouteDetectorMultipleRoutesDetectedDidChangeNotification object:protectedSelf->_routeDetector.get()];
 
             protectedSelf->_callback->externalOutputDeviceAvailableDidChange();
             END_BLOCK_OBJC_EXCEPTIONS
@@ -328,7 +359,7 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
     if (type == AVAudioSessionInterruptionTypeEnded && [[[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey] unsignedIntegerValue] == AVAudioSessionInterruptionOptionShouldResume)
         flags = PlatformMediaSession::MayResumePlaying;
 
-    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self), type, flags]() mutable {
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self), type, flags]() mutable {
         auto* callback = protectedSelf->_callback;
         if (!callback)
             return;
@@ -351,7 +382,7 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
     LOG(Media, "-[WebMediaSessionHelper applicationWillEnterForeground]");
 
     BOOL isSuspendedUnderLock = [[[notification userInfo] objectForKey:@"isSuspendedUnderLock"] boolValue];
-    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self), isSuspendedUnderLock]() mutable {
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self), isSuspendedUnderLock]() mutable {
         if (auto* callback = protectedSelf->_callback)
             callback->applicationWillEnterForeground(isSuspendedUnderLock);
     });
@@ -366,7 +397,7 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
 
     LOG(Media, "-[WebMediaSessionHelper applicationDidBecomeActive]");
 
-    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self)]() mutable {
         if (auto* callback = protectedSelf->_callback)
             callback->applicationDidBecomeActive();
     });
@@ -381,7 +412,7 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
 
     LOG(Media, "-[WebMediaSessionHelper applicationWillResignActive]");
 
-    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self)]() mutable {
         if (auto* callback = protectedSelf->_callback)
             callback->applicationWillBecomeInactive();
     });
@@ -396,7 +427,7 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
 
     LOG(Media, "-[WebMediaSessionHelper wirelessRoutesAvailableDidChange]");
 
-    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self)]() mutable {
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self)]() mutable {
         if (auto* callback = protectedSelf->_callback)
             callback->externalOutputDeviceAvailableDidChange();
     });
@@ -410,11 +441,42 @@ void MediaSessionManageriOS::externalOutputDeviceAvailableDidChange()
     LOG(Media, "-[WebMediaSessionHelper applicationDidEnterBackground]");
 
     BOOL isSuspendedUnderLock = [[[notification userInfo] objectForKey:@"isSuspendedUnderLock"] boolValue];
-    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = WTFMove(self), isSuspendedUnderLock]() mutable {
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self), isSuspendedUnderLock]() mutable {
         if (auto* callback = protectedSelf->_callback)
             callback->applicationDidEnterBackground(isSuspendedUnderLock);
     });
 }
+
+- (void)carPlayServerDied:(NSNotification *)notification
+{
+    if (!_callback)
+        return;
+
+    LOG(Media, "-[WebMediaSessionHelper carPlayServerDied:]");
+    UNUSED_PARAM(notification);
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self)]() mutable {
+        if (auto* callback = protectedSelf->_callback)
+            callback->carPlayServerDied();
+    });
+}
+
+- (void)carPlayIsConnectedDidChange:(NSNotification *)notification
+{
+    if (!_callback)
+        return;
+
+    Optional<bool> carPlayIsConnected;
+    if (notification && canLoadAVSystemController_CarPlayIsConnectedNotificationParameter()) {
+        NSNumber *nsCarPlayIsConnected = [[notification userInfo] valueForKey:getAVSystemController_CarPlayIsConnectedNotificationParameter()];
+        if (nsCarPlayIsConnected)
+            carPlayIsConnected = [nsCarPlayIsConnected boolValue];
+    }
+
+    callOnWebThreadOrDispatchAsyncOnMainThread([protectedSelf = retainPtr(self), carPlayIsConnected = WTFMove(carPlayIsConnected)]() mutable {
+        if (auto* callback = protectedSelf->_callback)
+            callback->updateCarPlayIsConnected(WTFMove(carPlayIsConnected));
+    });
+}
 @end
 
 #endif // PLATFORM(IOS_FAMILY)
index 510ef0d..2730dfe 100644 (file)
@@ -492,6 +492,7 @@ void Internals::resetToConsistentState(Page& page)
     PlatformMediaSessionManager::sharedManager().resetRestrictions();
     PlatformMediaSessionManager::sharedManager().setWillIgnoreSystemInterruptions(true);
 #endif
+    PlatformMediaSessionManager::sharedManager().setIsPlayingToAutomotiveHeadUnit(false);
 #if HAVE(ACCESSIBILITY)
     AXObjectCache::setEnhancedUserInterfaceAccessibility(false);
     AXObjectCache::disableAccessibility();
@@ -5067,4 +5068,9 @@ void Internals::setXHRMaximumIntervalForUserGestureForwarding(XMLHttpRequest& re
     request.setMaximumIntervalForUserGestureForwarding(interval);
 }
 
+void Internals::setIsPlayingToAutomotiveHeadUnit(bool isPlaying)
+{
+    PlatformMediaSessionManager::sharedManager().setIsPlayingToAutomotiveHeadUnit(isPlaying);
+}
+
 } // namespace WebCore
index 3ac66f7..46d426a 100644 (file)
@@ -823,6 +823,8 @@ public:
 
     void setXHRMaximumIntervalForUserGestureForwarding(XMLHttpRequest&, double);
 
+    void setIsPlayingToAutomotiveHeadUnit(bool);
+
 private:
     explicit Internals(Document&);
     Document* contextDocument() const;
index ca2bc54..3a59b4d 100644 (file)
@@ -753,4 +753,6 @@ enum CompositingPolicy {
     void testDictionaryLogging();
 
     void setXHRMaximumIntervalForUserGestureForwarding(XMLHttpRequest xhr, double interval);
+
+    void setIsPlayingToAutomotiveHeadUnit(boolean value);
 };