diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index 921d4bbce957..7e6aa5150fd8 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -4872,6 +4872,7 @@ imported/w3c/web-platform-tests/css/css-scroll-snap/scroll-target-snap-002.html webkit.org/b/218325 imported/w3c/web-platform-tests/css/css-scroll-snap/scroll-target-margin-001.html [ Pass ImageOnlyFailure ] # Cocoa-only +media/media-source/remoteplayback-from-source-element.html [ Skip ] http/tests/media/hls/hls-hdr-switch.html [ Skip ] http/tests/media/video-canplaythrough-webm.html [ Skip ] media/media-session/mock-coordinator.html [ Skip ] diff --git a/LayoutTests/media/media-source/remoteplayback-from-source-element-expected.txt b/LayoutTests/media/media-source/remoteplayback-from-source-element-expected.txt new file mode 100644 index 000000000000..d66ef99d3790 --- /dev/null +++ b/LayoutTests/media/media-source/remoteplayback-from-source-element-expected.txt @@ -0,0 +1,28 @@ + + +** Setup MSE and URL elements +EXPECTED (source.readyState == 'closed') OK +EVENT(sourceopen) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EVENT(update) +EXPECTED (video.currentSrc.indexOf("blob:") === '0') OK +EXPECTED (video.readyState >= '1') OK + +** Simulate a device becoming available + +** Simulate selecting a device +EVENT(connect) +EXPECTED (video.currentSrc.indexOf("blob:") < '0') OK +EXPECTED (video.readyState >= '1') OK + +END OF TEST + diff --git a/LayoutTests/media/media-source/remoteplayback-from-source-element.html b/LayoutTests/media/media-source/remoteplayback-from-source-element.html new file mode 100644 index 000000000000..7b8d96d05efb --- /dev/null +++ b/LayoutTests/media/media-source/remoteplayback-from-source-element.html @@ -0,0 +1,132 @@ + + + + Remote playback from source element + + + + + + + + + diff --git a/LayoutTests/platform/mac/TestExpectations b/LayoutTests/platform/mac/TestExpectations index 788c7c1402b4..f852b41318f5 100644 --- a/LayoutTests/platform/mac/TestExpectations +++ b/LayoutTests/platform/mac/TestExpectations @@ -94,6 +94,7 @@ http/tests/media/fairplay [ Pass ] media/track/track-description-cue.html [ Pass ] media/track/track-extended-descriptions.html [ Pass ] svg/filters/feDisplacementMap-filterUnits.svg [ Pass ] +media/media-source/remoteplayback-from-source-element.html [ Pass ] #////////////////////////////////////////////////////////////////////////////////////////// # End platform-specific directories. diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlayback.cpp b/Source/WebCore/Modules/remoteplayback/RemotePlayback.cpp index 31a80960580b..e242c3ceb7bf 100644 --- a/Source/WebCore/Modules/remoteplayback/RemotePlayback.cpp +++ b/Source/WebCore/Modules/remoteplayback/RemotePlayback.cpp @@ -53,6 +53,10 @@ Ref RemotePlayback::create(HTMLMediaElement& element) RemotePlayback::RemotePlayback(HTMLMediaElement& element) : WebCore::ActiveDOMObject(element.scriptExecutionContext()) +#if !RELEASE_LOG_DISABLED + , m_logger(element.logger()) + , m_logIdentifier(element.logIdentifier()) +#endif , m_mediaElement(element) { } @@ -79,8 +83,11 @@ void RemotePlayback::watchAvailability(Ref&& // 1. Let promise be a new promise-> // 2. Return promise, and run the following steps below: - - queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this, callback = WTFMove(callback), promise = WTFMove(promise)] () mutable { + + auto identifier = LOGIDENTIFIER; + ALWAYS_LOG(identifier); + + queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this, callback = WTFMove(callback), promise = WTFMove(promise), identifier = identifier] () mutable { if (isContextStopped()) return; @@ -89,7 +96,7 @@ void RemotePlayback::watchAvailability(Ref&& if (!m_mediaElement || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr) || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::disableremoteplaybackAttr)) { - WTFLogAlways("RemotePlayback::watchAvailability()::task - promise rejected"); + ERROR_LOG(identifier, "promise rejected, remote playback disabled"); promise->reject(InvalidStateError); return; } @@ -140,7 +147,10 @@ void RemotePlayback::cancelWatchAvailability(std::optional id, Ref // 2. Return promise, and run the following steps below: - queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this, id = WTFMove(id), promise = WTFMove(promise)] { + auto identifier = LOGIDENTIFIER; + ALWAYS_LOG(identifier); + + queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this, id = WTFMove(id), promise = WTFMove(promise), identifier = identifier] { if (isContextStopped()) return; // 3. If the disableRemotePlayback attribute is present for the media element, reject promise with @@ -148,6 +158,7 @@ void RemotePlayback::cancelWatchAvailability(std::optional id, RefhasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr) || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::disableremoteplaybackAttr)) { + ERROR_LOG(identifier, "promise rejected, remote playback disabled"); promise->reject(InvalidStateError); return; } @@ -162,6 +173,7 @@ void RemotePlayback::cancelWatchAvailability(std::optional id, Refreject(NotFoundError); return; } @@ -185,7 +197,10 @@ void RemotePlayback::prompt(Ref&& promise) // 1. Let promise be a new promise-> // 2. Return promise, and run the following steps below: - queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this, promise = WTFMove(promise), processingUserGesture = UserGestureIndicator::processingUserGesture()] () mutable { + auto identifier = LOGIDENTIFIER; + ALWAYS_LOG(identifier); + + queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this, promise = WTFMove(promise), processingUserGesture = UserGestureIndicator::processingUserGesture(), identifier = identifier] () mutable { if (isContextStopped()) return; @@ -194,6 +209,7 @@ void RemotePlayback::prompt(Ref&& promise) if (!m_mediaElement || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::webkitwirelessvideoplaybackdisabledAttr) || m_mediaElement->hasAttributeWithoutSynchronization(HTMLNames::disableremoteplaybackAttr)) { + ERROR_LOG(identifier, "promise rejected, remote playback disabled"); promise->reject(InvalidStateError); return; } @@ -207,6 +223,7 @@ void RemotePlayback::prompt(Ref&& promise) // is not feasible, reject promise with a NotSupportedError and abort all remaining steps. #if !PLATFORM(IOS) if (m_mediaElement->readyState() < HTMLMediaElementEnums::HAVE_METADATA) { + ERROR_LOG(identifier, "promise rejected, readyState = ", m_mediaElement->readyState()); promise->reject(NotSupportedError); return; } @@ -215,6 +232,7 @@ void RemotePlayback::prompt(Ref&& promise) // 6. If the algorithm isn't allowed to show a popup, reject promise with an InvalidAccessError exception // and abort these steps. if (!processingUserGesture) { + ERROR_LOG(identifier, "promise rejected, user gesture required"); promise->reject(InvalidAccessError); return; } @@ -231,6 +249,7 @@ void RemotePlayback::prompt(Ref&& promise) // 9. If the state is disconnected and availability for the media element is false, reject promise with a // NotSupportedError exception and abort all remaining steps. if (m_state == State::Disconnected && !m_available) { + ERROR_LOG(identifier, "promise rejected, state = ", m_state, ", available = ", m_available); promise->reject(NotSupportedError); return; } @@ -250,7 +269,7 @@ void RemotePlayback::shouldPlayToRemoteTargetChanged(bool shouldPlayToRemoteTarg // https://w3c.github.io/remote-playback/#prompt-user-for-changing-remote-playback-statee // W3C Editor's Draft 15 July 2016 - LOG(Media, "RemotePlayback::shouldPlayToRemoteTargetChanged(%p), shouldPlay(%d), promise count(%lu)", this, shouldPlayToRemoteTarget, m_promptPromises.size()); + ALWAYS_LOG(LOGIDENTIFIER, "shouldPlay = ", shouldPlayToRemoteTarget, ", promise count = ", m_promptPromises.size()); // 10. If the user picked a remote playback device device to initiate remote playback with, the user agent // must run the following steps: @@ -289,6 +308,7 @@ void RemotePlayback::setState(State state) if (m_state == state) return; + ALWAYS_LOG(LOGIDENTIFIER, state); m_state = state; auto eventName = [](State state) { @@ -331,6 +351,8 @@ void RemotePlayback::disconnect() if (m_state == State::Disconnected) return; + ALWAYS_LOG(LOGIDENTIFIER); + // 2. Queue a task to run the following steps: queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this] { if (isContextStopped()) @@ -403,6 +425,8 @@ void RemotePlayback::availabilityChanged(bool available) return; m_available = available; + ALWAYS_LOG(LOGIDENTIFIER); + queueTaskKeepingObjectAlive(*this, TaskSource::MediaElement, [this, available] { if (isContextStopped()) return; @@ -423,6 +447,13 @@ const char* RemotePlayback::activeDOMObjectName() const return "RemotePlayback"; } +#if !RELEASE_LOG_DISABLED +WTFLogChannel& RemotePlayback::logChannel() const +{ + return LogMedia; +} +#endif + } #endif // ENABLE(WIRELESS_PLAYBACK_TARGET) diff --git a/Source/WebCore/Modules/remoteplayback/RemotePlayback.h b/Source/WebCore/Modules/remoteplayback/RemotePlayback.h index 8b023fea0b55..49e87cf5b21b 100644 --- a/Source/WebCore/Modules/remoteplayback/RemotePlayback.h +++ b/Source/WebCore/Modules/remoteplayback/RemotePlayback.h @@ -31,6 +31,7 @@ #include "EventTarget.h" #include "WebCoreOpaqueRoot.h" #include +#include #include #include @@ -42,7 +43,11 @@ class MediaPlaybackTarget; class Node; class RemotePlaybackAvailabilityCallback; -class RemotePlayback final : public RefCounted, public ActiveDOMObject, public EventTarget { +class RemotePlayback final + : public RefCounted + , public ActiveDOMObject + , public EventTarget +{ WTF_MAKE_ISO_ALLOCATED(RemotePlayback); public: static Ref create(HTMLMediaElement&); @@ -90,6 +95,16 @@ class RemotePlayback final : public RefCounted, public ActiveDOM EventTargetInterface eventTargetInterface() const final { return RemotePlaybackEventTargetInterfaceType; } ScriptExecutionContext* scriptExecutionContext() const final { return ActiveDOMObject::scriptExecutionContext(); } +#if !RELEASE_LOG_DISABLED + const Logger& logger() const { return m_logger.get(); } + const void* logIdentifier() const { return m_logIdentifier; } + WTFLogChannel& logChannel() const; + const char* logClassName() const { return "RemotePlayback"; } + + Ref m_logger; + const void* m_logIdentifier { nullptr }; +#endif + WeakPtr m_mediaElement; uint32_t m_nextId { 0 }; diff --git a/Source/WebCore/html/HTMLMediaElement.cpp b/Source/WebCore/html/HTMLMediaElement.cpp index 78975ed39550..8c53b42b8d54 100644 --- a/Source/WebCore/html/HTMLMediaElement.cpp +++ b/Source/WebCore/html/HTMLMediaElement.cpp @@ -476,9 +476,6 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum , m_shouldVideoPlaybackRequireUserGesture(document.topDocument().videoPlaybackRequiresUserGesture() && !processingUserGestureForMedia()) , m_volumeLocked(defaultVolumeLocked()) , m_opaqueRootProvider([this] { return opaqueRoot(); }) -#if ENABLE(WIRELESS_PLAYBACK_TARGET) - , m_remote(RemotePlayback::create(*this)) -#endif #if USE(AUDIO_SESSION) , m_categoryAtMostRecentPlayback(AudioSessionCategory::None) #endif @@ -486,6 +483,9 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document& docum , m_logger(&document.logger()) , m_logIdentifier(uniqueLogIdentifier()) #endif +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + , m_remote(RemotePlayback::create(*this)) +#endif { allMediaElements().add(this); @@ -1070,18 +1070,31 @@ void HTMLMediaElement::scheduleCheckPlaybackTargetCompatability() void HTMLMediaElement::checkPlaybackTargetCompatibility() { #if ENABLE(WIRELESS_PLAYBACK_TARGET) - if (m_isPlayingToWirelessTarget && !m_player->canPlayToWirelessPlaybackTarget()) { - static const Seconds maxIntervalForWirelessPlaybackPlayerUpdate { 500_ms }; - Seconds delta = MonotonicTime::now() - m_currentPlaybackTargetIsWirelessEventFiredTime; - if (delta < maxIntervalForWirelessPlaybackPlayerUpdate) { - scheduleCheckPlaybackTargetCompatability(); - return; - } + if (!m_isPlayingToWirelessTarget || m_player->canPlayToWirelessPlaybackTarget()) + return; + static const Seconds maxIntervalForWirelessPlaybackPlayerUpdate { 500_ms }; + Seconds delta = MonotonicTime::now() - m_currentPlaybackTargetIsWirelessEventFiredTime; + if (delta < maxIntervalForWirelessPlaybackPlayerUpdate) { + scheduleCheckPlaybackTargetCompatability(); + return; + } + + auto tryToSwitchEngines = !m_remotePlaybackConfiguration && m_loadState == LoadingFromSourceElement; + if (tryToSwitchEngines) { + m_remotePlaybackConfiguration = { currentMediaTime(), playbackRate(), paused() }; + tryToSwitchEngines = havePotentialSourceChild(); + } + + if (!tryToSwitchEngines) { ERROR_LOG(LOGIDENTIFIER, "player incompatible after ", delta.value(), ", calling setShouldPlayToPlaybackTarget(false)"); m_failedToPlayToWirelessTarget = true; + m_remotePlaybackConfiguration = { }; m_player->setShouldPlayToPlaybackTarget(false); + return; } + + scheduleNextSourceChild(); #endif } @@ -1600,7 +1613,7 @@ void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentT bool loadAttempted = false; #if ENABLE(MEDIA_SOURCE) - if (!m_mediaSource && url.protocolIs(mediaSourceBlobProtocol)) + if (!m_mediaSource && url.protocolIs(mediaSourceBlobProtocol) && !m_remotePlaybackConfiguration) m_mediaSource = MediaSource::lookup(url.string()); if (m_mediaSource) { @@ -1621,7 +1634,7 @@ void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentT } #endif #if ENABLE(MEDIA_STREAM) - if (!loadAttempted && m_mediaStreamSrcObject) { + if (!loadAttempted && m_mediaStreamSrcObject && !m_remotePlaybackConfiguration) { loadAttempted = true; ALWAYS_LOG(LOGIDENTIFIER, "loading media stream blob ", m_mediaStreamSrcObject->logIdentifier()); if (!m_player->load(m_mediaStreamSrcObject->privateStream())) @@ -1629,7 +1642,7 @@ void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentT } #endif - if (!loadAttempted && m_blob) { + if (!loadAttempted && m_blob && !m_remotePlaybackConfiguration) { loadAttempted = true; ALWAYS_LOG(LOGIDENTIFIER, "loading generic blob"); if (!m_blobURLForReading.isEmpty()) @@ -1637,11 +1650,11 @@ void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentT m_blobURLForReading = BlobURL::createPublicURL(&document().securityOrigin()); ThreadableBlobRegistry::registerBlobURL(&document().securityOrigin(), document().policyContainer(), m_blobURLForReading, m_blob->url()); - if (!m_player->load(m_blobURLForReading, contentType, keySystem)) + if (!m_player->load(m_blobURLForReading, contentType, keySystem, !!m_remotePlaybackConfiguration)) mediaLoadingFailed(MediaPlayer::NetworkState::FormatError); } - if (!loadAttempted && !m_player->load(url, contentType, keySystem)) + if (!loadAttempted && !m_player->load(url, contentType, keySystem, !!m_remotePlaybackConfiguration)) mediaLoadingFailed(MediaPlayer::NetworkState::FormatError); mediaPlayerRenderingModeChanged(); @@ -2708,6 +2721,18 @@ void HTMLMediaElement::durationChanged() scheduleEvent(eventNames().durationchangeEvent); } +void HTMLMediaElement::applyConfiguration(const RemotePlaybackConfiguration& configuration) +{ + ALWAYS_LOG(LOGIDENTIFIER); + + if (configuration.currentTime) + setCurrentTime(configuration.currentTime); + if (configuration.rate != 1) + setPlaybackRate(configuration.rate); + if (!configuration.paused) + resumeAutoplaying(); +} + void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) { // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it @@ -2782,7 +2807,7 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) #if ENABLE(WIRELESS_PLAYBACK_TARGET) if (hasEventListeners(eventNames().webkitplaybacktargetavailabilitychangedEvent)) - enqueuePlaybackTargetAvailabilityChangedEvent(); + enqueuePlaybackTargetAvailabilityChangedEvent(EnqueueBehavior::OnlyWhenChanged); #endif m_initiallyMuted = m_volume < 0.05 || muted(); @@ -2824,6 +2849,13 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) if (!tracksAreReady) break; + if (oldState < HAVE_FUTURE_DATA && m_readyState >= HAVE_FUTURE_DATA) { + if (m_remotePlaybackConfiguration) { + applyConfiguration(*m_remotePlaybackConfiguration); + m_remotePlaybackConfiguration = { }; + } + } + if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA) { scheduleEvent(eventNames().canplayEvent); @@ -5098,7 +5130,7 @@ URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* ke UNUSED_PARAM(keySystem); // Don't log if this was just called to find out if there are any valid elements. - bool shouldLog = willLog(WTFLogLevel::Debug) && actionIfInvalid != DoNothing; + bool shouldLog = willLog(WTFLogLevel::Always) && actionIfInvalid != DoNothing; if (shouldLog) INFO_LOG(LOGIDENTIFIER); @@ -5152,6 +5184,7 @@ URL HTMLMediaElement::selectNextSourceChild(ContentType* contentType, String* ke #if ENABLE(MEDIA_STREAM) parameters.isMediaStream = mediaURL.protocolIs(mediaStreamBlobProtocol); #endif + parameters.requiresRemotePlayback = !!m_remotePlaybackConfiguration; if (!document().settings().allowMediaContentTypesRequiringHardwareSupportAsFallback() || Traversal::nextSkippingChildren(source)) parameters.contentTypesRequiringHardwareSupport = mediaContentTypesRequiringHardwareSupport(); @@ -6120,7 +6153,7 @@ void HTMLMediaElement::clearMediaPlayer() // Send an availability event in case scripts want to hide the picker when the element // doesn't support playback to a target. - enqueuePlaybackTargetAvailabilityChangedEvent(); + enqueuePlaybackTargetAvailabilityChangedEvent(EnqueueBehavior::Always); } if (m_isPlayingToWirelessTarget) @@ -6362,7 +6395,7 @@ void HTMLMediaElement::wirelessRoutesAvailableDidChange() bool hasTargets = mediaSession().hasWirelessPlaybackTargets(); m_remote->availabilityChanged(hasTargets); - enqueuePlaybackTargetAvailabilityChangedEvent(); + enqueuePlaybackTargetAvailabilityChangedEvent(EnqueueBehavior::OnlyWhenChanged); } void HTMLMediaElement::mediaPlayerCurrentPlaybackTargetIsWirelessChanged(bool isCurrentPlayBackTargetWireless) @@ -6377,12 +6410,13 @@ void HTMLMediaElement::setIsPlayingToWirelessTarget(bool isPlayingToWirelessTarg if (isContextStopped()) return; - UNUSED_PARAM(logSiteIdentifier); - - if (isPlayingToWirelessTarget == m_isPlayingToWirelessTarget) + auto newValue = isPlayingToWirelessTarget && m_player && m_player->isCurrentPlaybackTargetWireless(); + if (newValue == m_isPlayingToWirelessTarget) return; - m_isPlayingToWirelessTarget = m_player && m_player->isCurrentPlaybackTargetWireless(); + UNUSED_PARAM(logSiteIdentifier); + + m_isPlayingToWirelessTarget = newValue; m_remote->isPlayingToRemoteTargetChanged(m_isPlayingToWirelessTarget); ALWAYS_LOG(logSiteIdentifier, m_isPlayingToWirelessTarget); configureMediaControls(); @@ -6400,10 +6434,14 @@ void HTMLMediaElement::setIsPlayingToWirelessTarget(bool isPlayingToWirelessTarg }); } -void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent() +void HTMLMediaElement::enqueuePlaybackTargetAvailabilityChangedEvent(EnqueueBehavior behavior) { bool hasTargets = m_mediaSession && mediaSession().hasWirelessPlaybackTargets(); + if (behavior == EnqueueBehavior::OnlyWhenChanged && hasTargets == m_lastTargetAvailabilityEventState) + return; + ALWAYS_LOG(LOGIDENTIFIER, "hasTargets = ", hasTargets); + m_lastTargetAvailabilityEventState = hasTargets; auto event = WebKitPlaybackTargetAvailabilityEvent::create(eventNames().webkitplaybacktargetavailabilitychangedEvent, hasTargets); scheduleEvent(WTFMove(event)); scheduleUpdateMediaState(); @@ -6491,7 +6529,7 @@ bool HTMLMediaElement::addEventListener(const AtomString& eventType, RefhasAvailabilityCallbacks()) { m_hasPlaybackTargetAvailabilityListeners = true; mediaSession().setHasPlaybackTargetAvailabilityListeners(true); - enqueuePlaybackTargetAvailabilityChangedEvent(); // Ensure the event listener gets at least one event. + enqueuePlaybackTargetAvailabilityChangedEvent(EnqueueBehavior::Always); // Ensure the event listener gets at least one event. } #endif diff --git a/Source/WebCore/html/HTMLMediaElement.h b/Source/WebCore/html/HTMLMediaElement.h index 6267cf7ca456..09a313dab89d 100644 --- a/Source/WebCore/html/HTMLMediaElement.h +++ b/Source/WebCore/html/HTMLMediaElement.h @@ -748,7 +748,9 @@ class HTMLMediaElement #if ENABLE(WIRELESS_PLAYBACK_TARGET) void mediaPlayerCurrentPlaybackTargetIsWirelessChanged(bool) final; - void enqueuePlaybackTargetAvailabilityChangedEvent(); + + enum class EnqueueBehavior : uint8_t { Always, OnlyWhenChanged }; + void enqueuePlaybackTargetAvailabilityChangedEvent(EnqueueBehavior); #endif String mediaPlayerReferrer() const override; @@ -1002,6 +1004,13 @@ class HTMLMediaElement void playPlayer(); void pausePlayer(); + struct RemotePlaybackConfiguration { + MediaTime currentTime; + double rate; + bool paused; + }; + void applyConfiguration(const RemotePlaybackConfiguration&); + #if !RELEASE_LOG_DISABLED const void* mediaPlayerLogIdentifier() final { return logIdentifier(); } const Logger& mediaPlayerLogger() final { return logger(); } @@ -1235,10 +1244,6 @@ class HTMLMediaElement bool m_playbackBlockedWaitingForKey { false }; #endif -#if ENABLE(WIRELESS_PLAYBACK_TARGET) - Ref m_remote; -#endif - std::unique_ptr m_mediaSession; size_t m_reportedExtraMemoryCost { 0 }; @@ -1256,8 +1261,11 @@ class HTMLMediaElement MonotonicTime m_currentPlaybackTargetIsWirelessEventFiredTime; bool m_hasPlaybackTargetAvailabilityListeners { false }; bool m_failedToPlayToWirelessTarget { false }; + bool m_lastTargetAvailabilityEventState { false }; #endif + std::optional m_remotePlaybackConfiguration; + bool m_isPlayingToWirelessTarget { false }; bool m_playingOnSecondScreen { false }; bool m_removedBehaviorRestrictionsAfterFirstUserGesture { false }; @@ -1289,6 +1297,10 @@ class HTMLMediaElement RefPtr m_logger; const void* m_logIdentifier; #endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + Ref m_remote; +#endif }; String convertEnumerationToString(HTMLMediaElement::AutoplayEventPlaybackState); diff --git a/Source/WebCore/html/MediaElementSession.cpp b/Source/WebCore/html/MediaElementSession.cpp index 20fba9dbba15..320dcc4fd5d9 100644 --- a/Source/WebCore/html/MediaElementSession.cpp +++ b/Source/WebCore/html/MediaElementSession.cpp @@ -682,22 +682,22 @@ bool MediaElementSession::wantsToObserveViewportVisibilityForAutoplay() const #if ENABLE(WIRELESS_PLAYBACK_TARGET) void MediaElementSession::showPlaybackTargetPicker() { - INFO_LOG(LOGIDENTIFIER); + ALWAYS_LOG(LOGIDENTIFIER); auto& document = m_element.document(); if (m_restrictions & RequireUserGestureToShowPlaybackTargetPicker && !document.processingUserGestureForMedia()) { - INFO_LOG(LOGIDENTIFIER, "returning early because of permissions"); + ALWAYS_LOG(LOGIDENTIFIER, "returning early because of permissions"); return; } if (!document.page()) { - INFO_LOG(LOGIDENTIFIER, "returning early because page is NULL"); + ALWAYS_LOG(LOGIDENTIFIER, "returning early because page is NULL"); return; } #if !PLATFORM(IOS_FAMILY) if (m_element.readyState() < HTMLMediaElementEnums::HAVE_METADATA) { - INFO_LOG(LOGIDENTIFIER, "returning early because element is not playable"); + ALWAYS_LOG(LOGIDENTIFIER, "returning early because element is not playable"); return; } #endif diff --git a/Source/WebCore/platform/graphics/MediaPlayer.cpp b/Source/WebCore/platform/graphics/MediaPlayer.cpp index b360b4b36b03..34527518c878 100644 --- a/Source/WebCore/platform/graphics/MediaPlayer.cpp +++ b/Source/WebCore/platform/graphics/MediaPlayer.cpp @@ -480,7 +480,7 @@ void MediaPlayer::invalidate() m_client = &nullMediaPlayerClient(); } -bool MediaPlayer::load(const URL& url, const ContentType& contentType, const String& keySystem) +bool MediaPlayer::load(const URL& url, const ContentType& contentType, const String& keySystem, bool requiresRemotePlayback) { ASSERT(!m_reloadTimer.isActive()); @@ -490,6 +490,7 @@ bool MediaPlayer::load(const URL& url, const ContentType& contentType, const Str m_contentType = contentType; m_url = url; m_keySystem = keySystem.convertToASCIILowercase(); + m_requiresRemotePlayback = requiresRemotePlayback; m_contentMIMETypeWasInferredFromExtension = false; #if ENABLE(MEDIA_SOURCE) @@ -531,6 +532,7 @@ bool MediaPlayer::load(const URL& url, const ContentType& contentType, MediaSour m_contentType = contentType; m_url = url; m_keySystem = emptyString(); + m_requiresRemotePlayback = false; m_contentMIMETypeWasInferredFromExtension = false; loadWithNextMediaEngine(nullptr); return m_currentMediaEngine; @@ -544,6 +546,7 @@ bool MediaPlayer::load(MediaStreamPrivate& mediaStream) m_mediaStream = &mediaStream; m_keySystem = emptyString(); + m_requiresRemotePlayback = false; m_contentType = { }; m_contentMIMETypeWasInferredFromExtension = false; loadWithNextMediaEngine(nullptr); diff --git a/Source/WebCore/platform/graphics/MediaPlayer.h b/Source/WebCore/platform/graphics/MediaPlayer.h index 8fc4b379c5d3..59bf8fb942f9 100644 --- a/Source/WebCore/platform/graphics/MediaPlayer.h +++ b/Source/WebCore/platform/graphics/MediaPlayer.h @@ -98,6 +98,7 @@ struct MediaEngineSupportParameters { URL url; bool isMediaSource { false }; bool isMediaStream { false }; + bool requiresRemotePlayback { false }; Vector contentTypesRequiringHardwareSupport; std::optional> allowedMediaContainerTypes; std::optional> allowedMediaCodecTypes; @@ -112,6 +113,7 @@ struct MediaEngineSupportParameters { encoder << url; encoder << isMediaSource; encoder << isMediaStream; + encoder << requiresRemotePlayback; encoder << contentTypesRequiringHardwareSupport; encoder << allowedMediaContainerTypes; encoder << allowedMediaCodecTypes; @@ -127,6 +129,7 @@ struct MediaEngineSupportParameters { && decoder.decode(parameters.url) && decoder.decode(parameters.isMediaSource) && decoder.decode(parameters.isMediaStream) + && decoder.decode(parameters.requiresRemotePlayback) && decoder.decode(parameters.contentTypesRequiringHardwareSupport) && decoder.decode(parameters.allowedMediaContainerTypes) && decoder.decode(parameters.allowedMediaCodecTypes) @@ -358,7 +361,7 @@ class WEBCORE_EXPORT MediaPlayer : public MediaPlayerEnums, public ThreadSafeRef IntSize size() const { return m_size; } void setSize(const IntSize& size); - bool load(const URL&, const ContentType&, const String& keySystem); + bool load(const URL&, const ContentType&, const String&, bool); #if ENABLE(MEDIA_SOURCE) bool load(const URL&, const ContentType&, MediaSourcePrivateClient&); #endif @@ -717,6 +720,8 @@ class WEBCORE_EXPORT MediaPlayer : public MediaPlayerEnums, public ThreadSafeRef void setShouldDisableHDR(bool); bool shouldDisableHDR() const { return client().mediaPlayerShouldDisableHDR(); } + bool requiresRemotePlayback() const { return m_requiresRemotePlayback; } + private: MediaPlayer(MediaPlayerClient&); MediaPlayer(MediaPlayerClient&, MediaPlayerEnums::MediaEngineIdentifier); @@ -763,6 +768,7 @@ class WEBCORE_EXPORT MediaPlayer : public MediaPlayerEnums, public ThreadSafeRef bool m_shouldContinueAfterKeyNeeded { false }; #endif bool m_isGatheringVideoFrameMetadata { false }; + bool m_requiresRemotePlayback { false }; String m_lastErrorMessage; }; diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm index 1162726aeb24..f7384f0e35d8 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaStreamAVFObjC.mm @@ -228,7 +228,7 @@ void getSupportedTypes(HashSet& types) const f MediaPlayer::SupportsType MediaPlayerPrivateMediaStreamAVFObjC::supportsType(const MediaEngineSupportParameters& parameters) { - return parameters.isMediaStream ? MediaPlayer::SupportsType::IsSupported : MediaPlayer::SupportsType::IsNotSupported; + return (parameters.isMediaStream && !parameters.requiresRemotePlayback) ? MediaPlayer::SupportsType::IsSupported : MediaPlayer::SupportsType::IsNotSupported; } #pragma mark - diff --git a/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm b/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm index 28e17bcdfe1c..fe615595432a 100644 --- a/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm +++ b/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm @@ -140,7 +140,7 @@ static bool isCopyDisplayedPixelBufferAvailable() MediaPlayer::SupportsType MediaPlayerPrivateWebM::supportsType(const MediaEngineSupportParameters& parameters) { - if (parameters.isMediaSource || parameters.isMediaStream) + if (parameters.isMediaSource || parameters.isMediaStream || parameters.requiresRemotePlayback) return MediaPlayer::SupportsType::IsNotSupported; return SourceBufferParserWebM::isContentTypeSupported(parameters.type); diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp index ae574ae49fed..a2e63648debc 100644 --- a/Source/WebCore/testing/Internals.cpp +++ b/Source/WebCore/testing/Internals.cpp @@ -4776,16 +4776,18 @@ ExceptionOr> Internals::mediaElementCurrentlySpokenCue(HTMLMediaE #if ENABLE(WIRELESS_PLAYBACK_TARGET) void Internals::setMockMediaPlaybackTargetPickerEnabled(bool enabled) { - Page* page = contextDocument()->frame()->page(); - ASSERT(page); + auto frame = this->frame(); + if (!frame || !frame->page()) + return; - page->setMockMediaPlaybackTargetPickerEnabled(enabled); + frame->page()->setMockMediaPlaybackTargetPickerEnabled(enabled); } ExceptionOr Internals::setMockMediaPlaybackTargetPickerState(const String& deviceName, const String& deviceState) { - Page* page = contextDocument()->frame()->page(); - ASSERT(page); + auto frame = this->frame(); + if (!frame || !frame->page()) + return Exception { InvalidAccessError }; MediaPlaybackTargetContext::MockState state = MediaPlaybackTargetContext::MockState::Unknown; @@ -4798,18 +4800,18 @@ ExceptionOr Internals::setMockMediaPlaybackTargetPickerState(const String& else return Exception { InvalidAccessError }; - page->setMockMediaPlaybackTargetPickerState(deviceName, state); + frame->page()->setMockMediaPlaybackTargetPickerState(deviceName, state); return { }; } void Internals::mockMediaPlaybackTargetPickerDismissPopup() { - auto* page = contextDocument()->frame()->page(); - ASSERT(page); + auto frame = this->frame(); + if (!frame || !frame->page()) + return; - page->mockMediaPlaybackTargetPickerDismissPopup(); + frame->page()->mockMediaPlaybackTargetPickerDismissPopup(); } - #endif ExceptionOr> Internals::installMockPageOverlay(PageOverlayType type) diff --git a/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.cpp b/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.cpp index a4e1b60f7e7c..03183530f08c 100644 --- a/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.cpp +++ b/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.cpp @@ -143,7 +143,7 @@ void RemoteMediaPlayerProxy::getConfiguration(RemoteMediaPlayerConfiguration& co }); } -void RemoteMediaPlayerProxy::load(URL&& url, std::optional&& sandboxExtensionHandle, const ContentType& contentType, const String& keySystem, CompletionHandler&& completionHandler) +void RemoteMediaPlayerProxy::load(URL&& url, std::optional&& sandboxExtensionHandle, const ContentType& contentType, const String& keySystem, bool requiresRemotePlayback, CompletionHandler&& completionHandler) { RemoteMediaPlayerConfiguration configuration; if (sandboxExtensionHandle) { @@ -154,7 +154,7 @@ void RemoteMediaPlayerProxy::load(URL&& url, std::optionalload(url, contentType, keySystem); + m_player->load(url, contentType, keySystem, requiresRemotePlayback); getConfiguration(configuration); completionHandler(WTFMove(configuration)); } diff --git a/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.h b/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.h index ecb319fda8cc..48f89dd4f43e 100644 --- a/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.h +++ b/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.h @@ -134,7 +134,7 @@ class RemoteMediaPlayerProxy final void prepareForPlayback(bool privateMode, WebCore::MediaPlayerEnums::Preload, bool preservesPitch, bool prepareForRendering, float videoContentScale, WebCore::DynamicRangeMode, CompletionHandler&& inlineLayerHostingContextId)>&&); void prepareForRendering(); - void load(URL&&, std::optional&&, const WebCore::ContentType&, const String&, CompletionHandler&&); + void load(URL&&, std::optional&&, const WebCore::ContentType&, const String&, bool, CompletionHandler&&); #if ENABLE(MEDIA_SOURCE) void loadMediaSource(URL&&, const WebCore::ContentType&, bool webMParserEnabled, RemoteMediaSourceIdentifier, CompletionHandler&&); #endif diff --git a/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.messages.in b/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.messages.in index 1a609d0f2f32..fc90c1d324de 100644 --- a/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.messages.in +++ b/Source/WebKit/GPUProcess/media/RemoteMediaPlayerProxy.messages.in @@ -26,7 +26,7 @@ messages -> RemoteMediaPlayerProxy NotRefCounted { PrepareForPlayback(bool privateMode, enum:uint8_t WebCore::MediaPlayerEnums::Preload preload, bool preservesPitch, bool prepareForRendering, float videoContentScale, enum:uint8_t WebCore::DynamicRangeMode mode) -> (std::optional inlineLayerHostingContextId) - Load(URL url, std::optional sandboxExtension, WebCore::ContentType contentType, String keySystem) -> (struct WebKit::RemoteMediaPlayerConfiguration playerConfiguration) + Load(URL url, std::optional sandboxExtension, WebCore::ContentType contentType, String keySystem, bool requiresRemotePlayback) -> (struct WebKit::RemoteMediaPlayerConfiguration playerConfiguration) #if ENABLE(MEDIA_SOURCE) LoadMediaSource(URL url, WebCore::ContentType contentType, bool webMParserEnabled, WebKit::RemoteMediaSourceIdentifier mediaSourceIdentifier) -> (struct WebKit::RemoteMediaPlayerConfiguration playerConfiguration) #endif diff --git a/Source/WebKit/WebProcess/GPU/media/MediaPlayerPrivateRemote.cpp b/Source/WebKit/WebProcess/GPU/media/MediaPlayerPrivateRemote.cpp index 0c22a7ddd71f..d79f076ed799 100644 --- a/Source/WebKit/WebProcess/GPU/media/MediaPlayerPrivateRemote.cpp +++ b/Source/WebKit/WebProcess/GPU/media/MediaPlayerPrivateRemote.cpp @@ -204,7 +204,7 @@ void MediaPlayerPrivateRemote::load(const URL& url, const ContentType& contentTy sandboxExtensionHandle = WTFMove(handle); } - connection().sendWithAsyncReply(Messages::RemoteMediaPlayerProxy::Load(url, sandboxExtensionHandle, contentType, keySystem), [weakThis = WeakPtr { *this }, this](auto&& configuration) { + connection().sendWithAsyncReply(Messages::RemoteMediaPlayerProxy::Load(url, sandboxExtensionHandle, contentType, keySystem, m_player->requiresRemotePlayback()), [weakThis = WeakPtr { *this }, this](auto&& configuration) { if (!weakThis) return; diff --git a/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.cpp b/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.cpp index be9bb3a2b834..1bda2a27e735 100644 --- a/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.cpp +++ b/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.cpp @@ -72,7 +72,7 @@ MediaPlayerEnums::SupportsType RemoteMediaPlayerMIMETypeCache::supportsTypeAndCo if (parameters.type.raw().isEmpty()) return MediaPlayerEnums::SupportsType::MayBeSupported; - SupportedTypesAndCodecsKey searchKey { parameters.type.raw(), parameters.isMediaSource, parameters.isMediaStream }; + SupportedTypesAndCodecsKey searchKey { parameters.type.raw(), parameters.isMediaSource, parameters.isMediaStream, parameters.requiresRemotePlayback }; if (m_supportsTypeAndCodecsCache) { auto it = m_supportsTypeAndCodecsCache->find(searchKey); diff --git a/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.h b/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.h index ab8fc0e933e6..77375eb008cd 100644 --- a/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.h +++ b/Source/WebKit/WebProcess/GPU/media/RemoteMediaPlayerMIMETypeCache.h @@ -55,7 +55,7 @@ class RemoteMediaPlayerMIMETypeCache { RemoteMediaPlayerManager& m_manager; WebCore::MediaPlayerEnums::MediaEngineIdentifier m_engineIdentifier; - using SupportedTypesAndCodecsKey = std::tuple; + using SupportedTypesAndCodecsKey = std::tuple; std::optional> m_supportsTypeAndCodecsCache; HashSet m_supportedTypesCache; bool m_hasPopulatedSupportedTypesCacheFromGPUProcess { false };