Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -479,5 +479,156 @@ void MediaPlayerElementTests::ConfigureThenRemoveDefaultMediaPlayer()
});
}

void MediaPlayerElementTests::PausesAndResumesMediaPlayerWhenRemovedFromTree()
{
TestCleanupWrapper cleanup;

auto playingEvent = std::make_shared<Event>();
auto pausedEvent = std::make_shared<Event>();
auto playbackStateChangedRegistration = CreateSafeEventRegistration(MediaPlaybackSession, PlaybackStateChanged);
xaml_controls::Grid^ root = nullptr;
xaml_controls::MediaPlayerElement^ mpe = nullptr;
MediaPlayer^ player = nullptr;

RunOnUIThread([&]()
{
root = ref new xaml_controls::Grid();
mpe = ref new xaml_controls::MediaPlayerElement();
mpe->AutoPlay = true;
root->Children->Append(mpe);
TestServices::WindowHelper->WindowContent = root;
});
TestServices::WindowHelper->WaitForIdle();

RunOnUIThread([&]()
{
player = mpe->MediaPlayer;
VERIFY_IS_NOT_NULL(player);

playbackStateChangedRegistration.Attach(
player->PlaybackSession,
ref new wf::TypedEventHandler<MediaPlaybackSession^, Platform::Object^>(
[&](MediaPlaybackSession^ session, Platform::Object^)
{
if (session->PlaybackState == MediaPlaybackState::Playing)
{
playingEvent->Set();
}
else if (session->PlaybackState == MediaPlaybackState::Paused)
{
pausedEvent->Set();
}
}));

mpe->Source = ::Windows::Media::Core::MediaSource::CreateFromUri(
ref new Uri(GetResourcesPath() + L"testfile.wmv"));
});

playingEvent->WaitForDefault();

RunOnUIThread([&]()
{
VERIFY_ARE_EQUAL(MediaPlaybackState::Playing, player->PlaybackSession->PlaybackState);
root->Children->RemoveAt(0);
});

pausedEvent->WaitForDefault();

RunOnUIThread([&]()
{
VERIFY_ARE_EQUAL(MediaPlaybackState::Paused, player->PlaybackSession->PlaybackState);
VERIFY_ARE_EQUAL(player, mpe->MediaPlayer);

playingEvent->Reset();
root->Children->Append(mpe);
});

playingEvent->WaitForDefault();

RunOnUIThread([&]()
{
VERIFY_ARE_EQUAL(MediaPlaybackState::Playing, player->PlaybackSession->PlaybackState);
VERIFY_ARE_EQUAL(player, mpe->MediaPlayer);
});
}

void MediaPlayerElementTests::PausesMediaPlayerWhenWindowCloses()
{
auto playingEvent = std::make_shared<Event>();
auto pausedEvent = std::make_shared<Event>();
auto playbackStateChangedRegistration = CreateSafeEventRegistration(MediaPlaybackSession, PlaybackStateChanged);
Window^ window = nullptr;
xaml_controls::MediaPlayerElement^ mpe = nullptr;
MediaPlayer^ player = nullptr;
bool windowIsOpen = false;

TestCleanupWrapper cleanup([&]()
{
if (windowIsOpen)
{
RunOnUIThread([&]()
{
window->Close();
windowIsOpen = false;
window = nullptr;
mpe = nullptr;
player = nullptr;
});
}
});

RunOnUIThread([&]()
{
window = ref new Window();
mpe = ref new xaml_controls::MediaPlayerElement();
mpe->AutoPlay = true;
window->Content = mpe;
window->Activate();
windowIsOpen = true;
});
TestServices::WindowHelper->WaitForIdle();

RunOnUIThread([&]()
{
player = mpe->MediaPlayer;
VERIFY_IS_NOT_NULL(player);

playbackStateChangedRegistration.Attach(
player->PlaybackSession,
ref new wf::TypedEventHandler<MediaPlaybackSession^, Platform::Object^>(
[&](MediaPlaybackSession^ session, Platform::Object^)
{
if (session->PlaybackState == MediaPlaybackState::Playing)
{
playingEvent->Set();
}
else if (session->PlaybackState == MediaPlaybackState::Paused)
{
pausedEvent->Set();
}
}));

mpe->Source = ::Windows::Media::Core::MediaSource::CreateFromUri(
ref new Uri(GetResourcesPath() + L"testfile.wmv"));
});

playingEvent->WaitForDefault();

RunOnUIThread([&]()
{
VERIFY_ARE_EQUAL(MediaPlaybackState::Playing, player->PlaybackSession->PlaybackState);
window->Close();
windowIsOpen = false;
});

pausedEvent->WaitForDefault();

RunOnUIThread([&]()
{
VERIFY_ARE_EQUAL(MediaPlaybackState::Paused, player->PlaybackSession->PlaybackState);
VERIFY_ARE_EQUAL(player, mpe->MediaPlayer);
});
}

} } }
} } } }
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ namespace Microsoft { namespace UI { namespace Xaml { namespace Tests {
BEGIN_TEST_METHOD(ConfigureThenRemoveDefaultMediaPlayer)
END_TEST_METHOD()

BEGIN_TEST_METHOD(PausesAndResumesMediaPlayerWhenRemovedFromTree)
END_TEST_METHOD()

BEGIN_TEST_METHOD(PausesMediaPlayerWhenWindowCloses)
END_TEST_METHOD()

BEGIN_TEST_METHOD(ParseSourceUri)
TEST_METHOD_PROPERTY(L"Hosting:Mode", L"WPF")
END_TEST_METHOD()
Expand All @@ -66,4 +72,3 @@ namespace Microsoft { namespace UI { namespace Xaml { namespace Tests {

} } }
} } } }

48 changes: 48 additions & 0 deletions dxaml/xcp/dxaml/lib/MediaPlayerElement_partial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ MediaPlayerElement::OnPropertyChanged2(_In_ const PropertyChangedParams& args)
if (args.m_pDP->GetIndex() == KnownPropertyIndex::MediaPlayerElement_MediaPlayer)
{
IFC_RETURN(get_MediaPlayer(&m_spMediaPlayer));
m_resumeMediaPlayerOnEnter = false;
IFC_RETURN(UpdateTransportControlsState());
IFC_RETURN(UpdateAutoPlay());
IFC_RETURN(UpdateSource());
Expand Down Expand Up @@ -700,6 +701,12 @@ MediaPlayerElement::EnterImpl(
{
IFC_RETURN(UpdateAutoPlay());

if (m_resumeMediaPlayerOnEnter && m_spMediaPlayer)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_resumeMediaPlayerOnEnter is only cleared at line 267 for the MediaPlayer property and here at 706, so a Source change while the element is detached leaves it set and we force a Play() on the newly bound content on re-entry - that is the recycled container case for ListView and ItemsRepeater. It also bypasses AutoPlay="False", since UpdateAutoPlay() runs just before this, and it reverses a Pause() the app or user performed while we were out of the tree. Please check whether we should clear the flag in the Source case too, and re-read PlaybackState before resuming.

{
m_resumeMediaPlayerOnEnter = false;
LOG_IF_FAILED(m_spMediaPlayer->Play());
}

IFC_RETURN(UpdateTimedTextSource());
}

Expand All @@ -721,8 +728,49 @@ MediaPlayerElement::LeaveImpl(
IFC_RETURN(m_spTimedTextSource->SetMediaPlayer(nullptr));
}

bool shouldPauseMediaPlayer = bLive && !bVisualTreeBeingReset;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldPauseMediaPlayer = bLive && !bVisualTreeBeingReset means we pause on every ordinary removal from the live tree, not just on a window close - Frame navigation, ListView/ItemsRepeater container recycling, TabView content swap, re-parenting and Popup close all take this path. The linked bug is scoped to a terminal close, so we may have to consult IsShuttingDown() on both branches rather than treating !bVisualTreeBeingReset as sufficient. Please check whether the non-reset path should be pausing at all.

if (bLive && bVisualTreeBeingReset)
{
if (CContentRoot* contentRoot = VisualTree::GetContentRootForElement(GetHandle()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If GetContentRootForElement returns null here we fall through with shouldPauseMediaPlayer still false from line 731, so the one branch that actually implements the fix quietly does nothing and the bug reproduces with no assert or trace to explain why. Please check whether treating an unresolved content root as shutting down would be the safer default on a path we already know is a tree reset.

{
shouldPauseMediaPlayer = contentRoot->IsShuttingDown();
}
}

IFC_RETURN(__super::LeaveImpl(bLive, bSkipNameRegistration, bCoercedIsEnabled, bVisualTreeBeingReset));

if (shouldPauseMediaPlayer && m_spMediaPlayer && !m_resumeMediaPlayerOnEnter)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since e88e20cc removed m_bOwnsMediaPlayer there is no ownership signal left on the class, so this also pauses app-supplied players and players shared with a second MediaPlayerElement that is still live and on screen. SharedMediaPlayerSurvivesElementDestruction only pins the destruction path, so a shared player being paused by whichever element leaves first would not be caught. Please check whether we need a framework-created-player signal before mutating transport state here.

{
ctl::ComPtr<wmp::IMediaPlayer3> spMediaPlayer3;
ctl::ComPtr<wmp::IMediaPlaybackSession> spPlaybackSession;
wmp::MediaPlaybackState playbackState{};

HRESULT stateResult = m_spMediaPlayer.As(&spMediaPlayer3);
if (SUCCEEDED(stateResult))
{
stateResult = spMediaPlayer3->get_PlaybackSession(&spPlaybackSession);
}
if (SUCCEEDED(stateResult) && spPlaybackSession)
{
stateResult = spPlaybackSession->get_PlaybackState(&playbackState);
}
else if (SUCCEEDED(stateResult))
{
stateResult = E_UNEXPECTED;
}

if (SUCCEEDED(stateResult) &&
(playbackState == wmp::MediaPlaybackState_Playing ||
playbackState == wmp::MediaPlaybackState_Buffering))
{
const HRESULT pauseResult = m_spMediaPlayer->Pause();
if (SUCCEEDED(pauseResult))
{
m_resumeMediaPlayerOnEnter = true;
}
}
}

return S_OK;
}

Expand Down
1 change: 1 addition & 0 deletions dxaml/xcp/dxaml/lib/MediaPlayerElement_partial.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,6 @@ namespace DirectUI
ctl::ComPtr<wmp::IMediaPlayer> m_spMediaPlayer;
ctl::ComPtr<CTimedTextSource> m_spTimedTextSource;
bool m_bInit;
bool m_resumeMediaPlayerOnEnter{ false };
};
}
Loading