Commit 5f0a846a authored by Thomas Guilbert's avatar Thomas Guilbert Committed by Commit Bot

Add XR support to video.rVFC

Currently, when we enter an immersive XRSession, window.rAF calls stops,
which means we no longer deliver video.requestVideoFrameCallback calls.

This CL adds video.rVFC support to immersive XRSessions. If we find an
immersive XRSession, we schedule video.rVFC callbacks there instead of
the ScriptedAnimationController (SAC).

There can only be one immersive XRSession. If we don't find a session,
but we find that the XRFrameProvider exists, we request to be notified
when an immersive session start. This allows us to keep callbacks going
if the callbacks were already scheduled in the SAC when starting an
XRSession. Simmilarly, if the session ends when callbacks were pending
in the XRSession, we reschedule ourselves in the SAC.

Bug: 1107578
Change-Id: If0acf9445bb789bc848e0043908fc9b769cc5bdd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2406522
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Reviewed-by: default avatarKlaus Weidner <klausw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#807653}
parent 09a564d7
......@@ -11,4 +11,6 @@ blink_modules_sources("video_rvfc") {
"video_frame_request_callback_collection.cc",
"video_frame_request_callback_collection.h",
]
deps = [ "//third_party/blink/renderer/modules/xr:xr" ]
}
......@@ -16,6 +16,10 @@
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/core/timing/time_clamper.h"
#include "third_party/blink/renderer/modules/video_rvfc/video_frame_request_callback_collection.h"
#include "third_party/blink/renderer/modules/xr/navigator_xr.h"
#include "third_party/blink/renderer/modules/xr/xr_frame_provider.h"
#include "third_party/blink/renderer/modules/xr/xr_session.h"
#include "third_party/blink/renderer/modules/xr/xr_system.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
......@@ -80,21 +84,82 @@ void VideoFrameCallbackRequesterImpl::OnWebMediaPlayerCreated() {
GetSupplementable()->GetWebMediaPlayer()->RequestVideoFrameCallback();
}
void VideoFrameCallbackRequesterImpl::ScheduleCallbackExecution() {
TRACE_EVENT1("blink",
"VideoFrameCallbackRequesterImpl::ScheduleCallbackExecution",
void VideoFrameCallbackRequesterImpl::ScheduleWindowRaf() {
GetSupplementable()
->GetDocument()
.GetScriptedAnimationController()
.ScheduleVideoFrameCallbacksExecution(
WTF::Bind(&VideoFrameCallbackRequesterImpl::OnExecution,
WrapWeakPersistent(this)));
}
void VideoFrameCallbackRequesterImpl::ScheduleExecution() {
TRACE_EVENT1("blink", "VideoFrameCallbackRequesterImpl::ScheduleExecution",
"did_schedule", !pending_execution_);
if (pending_execution_)
return;
pending_execution_ = true;
GetSupplementable()
->GetDocument()
.GetScriptedAnimationController()
.ScheduleVideoFrameCallbacksExecution(
WTF::Bind(&VideoFrameCallbackRequesterImpl::OnRenderingSteps,
WrapWeakPersistent(this)));
if (TryScheduleImmersiveXRSessionRaf())
return;
ScheduleWindowRaf();
}
void VideoFrameCallbackRequesterImpl::OnImmersiveSessionStart() {
listening_for_immersive_session_ = false;
TryScheduleImmersiveXRSessionRaf();
}
void VideoFrameCallbackRequesterImpl::OnXrFrame(bool ended, double timestamp) {
if (ended) {
// The immersive XRSession has ended, and we shouldn't run callbacks as part
// of XRSession.rAF. Instead, ask to be run as part of the next window.rAF
// callbacks.
ScheduleWindowRaf();
return;
}
OnExecution(timestamp);
}
bool VideoFrameCallbackRequesterImpl::TryScheduleImmersiveXRSessionRaf() {
auto& document = GetSupplementable()->GetDocument();
// Nothing to do here, we will be notified via OnImmersiveSessionStart() when
// a new immersive session starts.
if (listening_for_immersive_session_)
return false;
// Do not force the lazy creation of the NavigatorXR by accessing it through
// NavigatorXR::From(). If it doesn't exist already exist, the webpage isn't
// using XR.
if (!NavigatorXR::AlreadyExists(document))
return false;
auto* system = NavigatorXR::From(document)->xr();
if (!system)
return false;
XRSession* session = system->frameProvider()->immersive_session();
if (session && !session->ended()) {
session->ScheduleVideoFrameCallbacksExecution(WTF::Bind(
&VideoFrameCallbackRequesterImpl::OnXrFrame, WrapWeakPersistent(this)));
return true;
}
DCHECK(!listening_for_immersive_session_);
system->frameProvider()->AddImmersiveSessionStartCallback(
WTF::Bind(&VideoFrameCallbackRequesterImpl::OnImmersiveSessionStart,
WrapWeakPersistent(this)));
listening_for_immersive_session_ = true;
return false;
}
void VideoFrameCallbackRequesterImpl::OnRequestVideoFrameCallback() {
......@@ -107,7 +172,7 @@ void VideoFrameCallbackRequesterImpl::OnRequestVideoFrameCallback() {
if (callback_collection_->IsEmpty())
return;
ScheduleCallbackExecution();
ScheduleExecution();
}
void VideoFrameCallbackRequesterImpl::ExecuteVideoFrameCallbacks(
......@@ -165,15 +230,15 @@ void VideoFrameCallbackRequesterImpl::ExecuteVideoFrameCallbacks(
callback_collection_->ExecuteFrameCallbacks(high_res_now_ms, metadata);
}
void VideoFrameCallbackRequesterImpl::OnRenderingSteps(double high_res_now_ms) {
DCHECK(pending_execution_);
void VideoFrameCallbackRequesterImpl::OnExecution(double high_res_now_ms) {
TRACE_EVENT1("blink", "VideoFrameCallbackRequesterImpl::OnRenderingSteps",
"has_callbacks", !callback_collection_->IsEmpty());
pending_execution_ = false;
// Callbacks could have been canceled from the time we scheduled their
// execution.
// We could also be executing a leftover callback scheduled through the
// ScriptedAnimationController, right after exiting an immersive XR session.
if (callback_collection_->IsEmpty())
return;
......@@ -204,7 +269,7 @@ void VideoFrameCallbackRequesterImpl::OnRenderingSteps(double high_res_now_ms) {
// extra rendering steps would be wasteful.
if (is_hfr && !callback_collection_->IsEmpty() &&
consecutive_stale_frames_ < 2) {
ScheduleCallbackExecution();
ScheduleExecution();
}
}
......
......@@ -40,8 +40,11 @@ class MODULES_EXPORT VideoFrameCallbackRequesterImpl final
void OnRequestVideoFrameCallback() override;
// Called by ScriptedAnimationController as part of the rendering steps,
// right before the execution of window.rAF callbacks.
void OnRenderingSteps(double high_res_now_ms);
// right before executing window.rAF callbacks. Also called by OnXRFrame().
void OnExecution(double high_res_now_ms);
// Called by XRSession right before executing xr_session.rAF callbacks.
void OnXrFrame(bool ended, double timestamp);
private:
friend class VideoFrameCallbackRequesterImplTest;
......@@ -60,10 +63,24 @@ class MODULES_EXPORT VideoFrameCallbackRequesterImpl final
void RegisterCallbackForTest(
VideoFrameRequestCallbackCollection::VideoFrameCallback*);
// Adds |this| to the ScriptedAnimationController's queue of video.rAF
// Queues up |callback_collection_| to be run before the next window.rAF, or
// xr_session.rAF if we are an immersive XR session.
void ScheduleExecution();
// Adds |this| to the ScriptedAnimationController's queue of video.rVFC
// callbacks that should be executed during the next rendering steps.
// Also causes rendering steps to be scheduled if needed.
void ScheduleCallbackExecution();
void ScheduleWindowRaf();
// Check whether there is an immersive XR session, and adds |this| to the list
// of video.rVFC callbacks that should be run the next time there is an XR
// frame. Requests a new XR frame if needed.
// Returns true if we scheduled ourselves, false if there is no immersive XR
// session.
bool TryScheduleImmersiveXRSessionRaf();
// Called when an immersive XR Session is started.
void OnImmersiveSessionStart();
// Used to keep track of whether or not we have already scheduled a call to
// ExecuteFrameCallbacks() in the next rendering steps.
......@@ -81,6 +98,10 @@ class MODULES_EXPORT VideoFrameCallbackRequesterImpl final
// getting new frames.
int consecutive_stale_frames_ = 0;
// Indicates whether or not we already notified the XR Frame provider that we
// want to be notified when a new immersive XR session starts.
bool listening_for_immersive_session_ = false;
Member<VideoFrameRequestCallbackCollection> callback_collection_;
DISALLOW_COPY_AND_ASSIGN(VideoFrameCallbackRequesterImpl);
......
......@@ -58,12 +58,10 @@ class MockFunction : public ScriptFunction {
class MetadataHelper {
public:
static VideoFramePresentationMetadata* GetDefaultMedatada() {
DCHECK(initialized);
return &metadata_;
}
static std::unique_ptr<VideoFramePresentationMetadata> CopyDefaultMedatada() {
DCHECK(initialized);
auto copy = std::make_unique<VideoFramePresentationMetadata>();
copy->presented_frames = metadata_.presented_frames;
......@@ -77,10 +75,10 @@ class MetadataHelper {
return copy;
}
static void InitializeFields(base::TimeTicks now) {
if (initialized)
return;
// This method should be called by each test, passing in its own
// DocumentLoadTiming::ReferenceMonotonicTime(). Otherwise, we will run into
// clamping verification test issues, as described below.
static void ReinitializeFields(base::TimeTicks now) {
// We don't want any time ticks be a multiple of 5us, otherwise, we couldn't
// tell whether or not the implementation clamped their values. Therefore,
// we manually set the values for a deterministic test, and make sure we
......@@ -101,16 +99,12 @@ class MetadataHelper {
metadata_.metadata.receive_time =
now + base::TimeDelta::FromMillisecondsD(17.1234);
metadata_.metadata.rtp_timestamp = 12345;
initialized = true;
}
private:
static bool initialized;
static VideoFramePresentationMetadata metadata_;
};
bool MetadataHelper::initialized = false;
VideoFramePresentationMetadata MetadataHelper::metadata_;
// Helper class that compares the parameters used when invoking a callback, with
......@@ -326,9 +320,9 @@ TEST_F(VideoFrameCallbackRequesterImplTest,
testing::Mock::VerifyAndClear(function);
}
TEST_F(VideoFrameCallbackRequesterImplTest, VerifyParameters) {
TEST_F(VideoFrameCallbackRequesterImplTest, VerifyParameters_WindowRaf) {
auto timing = GetDocument().Loader()->GetTiming();
MetadataHelper::InitializeFields(timing.ReferenceMonotonicTime());
MetadataHelper::ReinitializeFields(timing.ReferenceMonotonicTime());
auto* callback =
MakeGarbageCollected<VfcRequesterParameterVerifierCallback>(timing);
......@@ -345,7 +339,68 @@ TEST_F(VideoFrameCallbackRequesterImplTest, VerifyParameters) {
// Run the callbacks directly, since they weren't scheduled to be run by the
// ScriptedAnimationController.
vfc_requester().OnRenderingSteps(now_ms);
vfc_requester().OnExecution(now_ms);
EXPECT_EQ(callback->last_now(), now_ms);
EXPECT_TRUE(callback->was_invoked());
testing::Mock::VerifyAndClear(media_player());
}
TEST_F(VideoFrameCallbackRequesterImplTest, VerifyParameters_XRSession_rAF) {
auto timing = GetDocument().Loader()->GetTiming();
MetadataHelper::ReinitializeFields(timing.ReferenceMonotonicTime());
auto* callback =
MakeGarbageCollected<VfcRequesterParameterVerifierCallback>(timing);
// Register the non-V8 callback.
RegisterCallbackDirectly(callback);
EXPECT_CALL(*media_player(), GetVideoFramePresentationMetadata())
.WillOnce(Return(ByMove(MetadataHelper::CopyDefaultMedatada())));
const double now_ms =
timing.MonotonicTimeToZeroBasedDocumentTime(base::TimeTicks::Now())
.InMillisecondsF();
// Run the callbacks directly, as if scheduled by the XRSession.
vfc_requester().OnXrFrame(/* ended */ false, now_ms);
EXPECT_EQ(callback->last_now(), now_ms);
EXPECT_TRUE(callback->was_invoked());
testing::Mock::VerifyAndClear(media_player());
}
TEST_F(VideoFrameCallbackRequesterImplTest, VerifyParameters_XRSession_Ended) {
auto timing = GetDocument().Loader()->GetTiming();
MetadataHelper::ReinitializeFields(timing.ReferenceMonotonicTime());
auto* callback =
MakeGarbageCollected<VfcRequesterParameterVerifierCallback>(timing);
// Register the non-V8 callback.
RegisterCallbackDirectly(callback);
EXPECT_CALL(*media_player(), GetVideoFramePresentationMetadata()).Times(0);
// Simulate the XRSession ending.
vfc_requester().OnXrFrame(/* ended */ true, 0.0);
// Calbacks should not have been run...
testing::Mock::VerifyAndClear(media_player());
auto now = base::TimeTicks::Now();
auto now_ms =
timing.MonotonicTimeToZeroBasedDocumentTime(now).InMillisecondsF();
EXPECT_CALL(*media_player(), GetVideoFramePresentationMetadata())
.WillOnce(Return(ByMove(MetadataHelper::CopyDefaultMedatada())));
// ... But there should have been a call to schedule the callbacks with the
// ScriptedAnimationController when the XR session ended.
SimulateVideoFrameCallback(now);
EXPECT_EQ(callback->last_now(), now_ms);
EXPECT_TRUE(callback->was_invoked());
......
......@@ -128,8 +128,7 @@ TEST_F(VideoFrameRequestCallbackCollectionTest, CreateCallbackDuringExecution) {
EXPECT_TRUE(collection()->IsEmpty());
}
TEST_F(VideoFrameRequestCallbackCollectionTest,
CancelCallbgitackDuringExecution) {
TEST_F(VideoFrameRequestCallbackCollectionTest, CancelCallbackDuringExecution) {
auto dummy_callback = CreateCallback();
CallbackId dummy_callback_id =
collection()->RegisterFrameCallback(dummy_callback.Get());
......
......@@ -17,6 +17,14 @@ namespace blink {
const char NavigatorXR::kSupplementName[] = "NavigatorXR";
bool NavigatorXR::AlreadyExists(Document& document) {
if (!document.GetFrame())
return false;
Navigator& navigator = *document.GetFrame()->DomWindow()->navigator();
return !!Supplement<Navigator>::From<NavigatorXR>(navigator);
}
NavigatorXR* NavigatorXR::From(Document& document) {
if (!document.GetFrame())
return nullptr;
......
......@@ -23,6 +23,10 @@ class MODULES_EXPORT NavigatorXR final : public GarbageCollected<NavigatorXR>,
static NavigatorXR* From(Document&);
static NavigatorXR& From(Navigator&);
// Allows us to check whether |Document| has a NavigatorXR, without triggering
// its lazy creation.
static bool AlreadyExists(Document&);
explicit NavigatorXR(Navigator&);
static XRSystem* xr(Navigator&);
......
......@@ -60,6 +60,11 @@ XRFrameProvider::XRFrameProvider(XRSystem* xr)
immersive_presentation_provider_(xr->GetExecutionContext()),
last_has_focus_(xr->IsFrameFocused()) {}
void XRFrameProvider::AddImmersiveSessionStartCallback(
ImmersiveSessionStartCallback callback) {
immersive_session_start_callbacks_.push_back(std::move(callback));
}
void XRFrameProvider::OnSessionStarted(
XRSession* session,
device::mojom::blink::XRSessionPtr session_ptr) {
......@@ -73,6 +78,13 @@ void XRFrameProvider::OnSessionStarted(
immersive_session_ = session;
if (!immersive_session_start_callbacks_.IsEmpty()) {
Vector<ImmersiveSessionStartCallback> callbacks;
immersive_session_start_callbacks_.swap(callbacks);
for (auto& callback : callbacks)
std::move(callback).Run();
}
immersive_data_provider_.Bind(
std::move(session_ptr->data_provider),
xr_->GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI));
......
......@@ -26,6 +26,8 @@ class XRWebGLLayer;
// pose information for a given XRDevice.
class XRFrameProvider final : public GarbageCollected<XRFrameProvider> {
public:
using ImmersiveSessionStartCallback = base::OnceClosure;
explicit XRFrameProvider(XRSystem*);
XRSession* immersive_session() const { return immersive_session_; }
......@@ -54,6 +56,8 @@ class XRFrameProvider final : public GarbageCollected<XRFrameProvider> {
return immersive_data_provider_.get();
}
void AddImmersiveSessionStartCallback(ImmersiveSessionStartCallback);
virtual void Trace(Visitor*) const;
private:
......@@ -120,6 +124,8 @@ class XRFrameProvider final : public GarbageCollected<XRFrameProvider> {
HeapHashMap<Member<XRSession>, device::mojom::blink::XRFrameDataPtr>
requesting_sessions_;
Vector<ImmersiveSessionStartCallback> immersive_session_start_callbacks_;
// This frame ID is XR-specific and is used to track when frames arrive at the
// XR compositor so that it knows which poses to use, when to apply bounds
// updates, etc.
......
......@@ -683,6 +683,19 @@ XRSession::GetStationaryReferenceSpace() const {
return result;
}
void XRSession::ScheduleVideoFrameCallbacksExecution(
ExecuteVfcCallback execute_vfc_callback) {
vfc_execution_queue_.push_back(std::move(execute_vfc_callback));
MaybeRequestFrame();
}
void XRSession::ExecuteVideoFrameCallbacks(bool ended, double timestamp) {
Vector<ExecuteVfcCallback> execute_vfc_callbacks;
vfc_execution_queue_.swap(execute_vfc_callbacks);
for (auto& callback : execute_vfc_callbacks)
std::move(callback).Run(ended, timestamp);
}
int XRSession::requestAnimationFrame(V8XRFrameRequestCallback* callback) {
DVLOG(3) << __func__;
......@@ -1213,6 +1226,10 @@ void XRSession::ForceEnd(ShutdownPolicy shutdown_policy) {
ended_ = true;
pending_frame_ = false;
// Clear any pending callbacks. The timestamp is ignored by the callee.
constexpr double kIgnoredTimestamp = 0.0;
ExecuteVideoFrameCallbacks(/* ended */ true, kIgnoredTimestamp);
for (unsigned i = 0; i < input_sources_->length(); i++) {
auto* input_source = (*input_sources_)[i];
input_source->OnRemoved();
......@@ -1382,7 +1399,8 @@ void XRSession::MaybeRequestFrame() {
// If we have an outstanding callback registered, then we know that the page
// actually wants frames.
bool page_wants_frame = !callback_collection_->IsEmpty();
bool page_wants_frame =
!callback_collection_->IsEmpty() || !vfc_execution_queue_.IsEmpty();
// A page can process frames if it has its appropriate base layer set and has
// indicated that it actually wants frames.
......@@ -1633,6 +1651,7 @@ void XRSession::OnFrame(
// happen within these calls. resolving_frame_ will be true for the duration
// of the callbacks.
base::AutoReset<bool> resolving(&resolving_frame_, true);
ExecuteVideoFrameCallbacks(/* ended */ false, timestamp);
callback_collection_->ExecuteCallbacks(this, timestamp, presentation_frame);
// The session might have ended in the middle of the frame. Only call
......@@ -2063,7 +2082,9 @@ Vector<XRViewData>& XRSession::views() {
}
bool XRSession::HasPendingActivity() const {
return !callback_collection_->IsEmpty() && !ended_;
return (!callback_collection_->IsEmpty() ||
!vfc_execution_queue_.IsEmpty()) &&
!ended_;
}
void XRSession::Trace(Visitor* visitor) const {
......
......@@ -77,6 +77,16 @@ class XRSession final
static constexpr char kAnchorsFeatureNotSupported[] =
"Anchors feature is not supported by the session.";
// Runs all the video.requestVideoFrameCallback() callbacks associated with
// one HTMLVideoElement.
// - |bool| is whether or not the session has ended.
// - |double| is the |high_res_now_ms|, derived from
// MonotonicTimeToZeroBasedDocumentTime(|current_frame_time|), to be passed
// as the "now" parameter when executing rVFC callbacks. In other words, a
// video.rVFC and an xrSession.rAF callback share the same "now" parameters
// if they are run in the same turn of the render loop.
using ExecuteVfcCallback = base::OnceCallback<void(bool, double)>;
enum EnvironmentBlendMode {
kBlendModeOpaque = 0,
kBlendModeAdditive,
......@@ -345,6 +355,10 @@ class XRSession final
// Sets the metrics reporter for this session. This should only be done once.
void SetMetricsReporter(std::unique_ptr<MetricsReporter> reporter);
// Queues up the execution of video.requestVideoFrameCallback() callbacks for
// a specific HTMLVideoELement, for the next requestAnimationFrame() call.
void ScheduleVideoFrameCallbacksExecution(ExecuteVfcCallback);
private:
class XRSessionResizeObserverDelegate;
......@@ -412,6 +426,8 @@ class XRSession final
void HandleShutdown();
void ExecuteVideoFrameCallbacks(bool ended, double timestamp);
const Member<XRSystem> xr_;
const device::mojom::blink::XRSessionMode mode_;
const bool environment_integration_;
......@@ -524,6 +540,9 @@ class XRSession final
HeapMojoWrapperMode::kWithoutContextObserver>
input_receiver_;
// Used to schedule video.rVFC callbacks for immersive sessions.
Vector<ExecuteVfcCallback> vfc_execution_queue_;
Member<XRFrameRequestCallbackCollection> callback_collection_;
// Viewer pose in mojo space.
std::unique_ptr<TransformationMatrix> mojo_from_viewer_;
......
<!DOCTYPE html>
<html>
<title>Test that video.rVFC callbacks started before an XRSession work.</title>
<body>
<canvas/>
</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/media.js"></script>
<script src="../webxr/resources/webxr_util.js"></script>
<script src="../webxr/resources/webxr_test_constants.js"></script>
<script>
// Start the video.rVFC callbacks before starting the XR Session.
let video = document.createElement('video');
video.src = getVideoURI('/media/movie_5');
var numberVFCs = 0;
let videoCallback = () => {
numberVFCs++;
video.requestVideoFrameCallback(videoCallback);
}
video.requestVideoFrameCallback(videoCallback);
video.play();
let testFunction = async function(session, fakeDeviceController, t) {
let watcherDone = new Event("watcherdone");
let eventWatcher = new EventWatcher(t, session, ["end", "watcherdone"]);
let eventPromise = eventWatcher.wait_for(["end", "watcherdone"]);
numberVFCs = 0;
function onXRFrame(time, frame) {
if(numberVFCs >= 2) {
// Make sure video.rVFCs are still coming through before ending the
// session.
session.end();
}
session.requestAnimationFrame(onXRFrame);
}
function onSessionEnd(event) {
// Make sure we are still getting rVFC callbacks after the session end.
numberVFCs = 0;
t.step_wait_func(() => numberVFCs >= 2,
() => session.dispatchEvent(watcherDone),
"Time out waiting for VFC callbacks");
}
session.addEventListener("end", onSessionEnd, false);
session.requestAnimationFrame(onXRFrame);
return eventPromise;
}
xr_session_promise_test('Make sure video.rVFC works during a non-immersive session',
testFunction, TRACKED_IMMERSIVE_DEVICE, 'inline');
video.currentTime = 0;
xr_session_promise_test('Make sure video.rVFC works during an immersive session',
testFunction, TRACKED_IMMERSIVE_DEVICE, 'immersive-vr');
</script>
</html>
<!DOCTYPE html>
<html>
<title>Test that video.rVFC callbacks started during an XRSession work.</title>
<body>
<canvas/>
</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/media.js"></script>
<script src="../webxr/resources/webxr_util.js"></script>
<script src="../webxr/resources/webxr_test_constants.js"></script>
<script>
let testFunction = async function(session, fakeDeviceController, t) {
let watcherDone = new Event("watcherdone");
let eventWatcher = new EventWatcher(t, session, ["end", "watcherdone"]);
let eventPromise = eventWatcher.wait_for(["end", "watcherdone"]);
// Start the video.rVFC callbacks while we are in the the XR Session.
let video = document.createElement('video');
video.src = getVideoURI('/media/movie_5');
var numberVFCs = 0;
let videoCallback = () => {
numberVFCs++;
video.requestVideoFrameCallback(videoCallback);
}
video.requestVideoFrameCallback(videoCallback);
video.play();
function onXRFrame(time, frame) {
if(numberVFCs >= 2) {
// Make sure video.rVFCs are coming through before ending the
// session.
session.end();
}
session.requestAnimationFrame(onXRFrame);
}
function onSessionEnd(event) {
// Make sure we are still getting rVFC callbacks after the session end.
numberVFCs = 0;
t.step_wait_func(() => numberVFCs >= 2,
() => session.dispatchEvent(watcherDone),
"Time out waiting for VFC callbacks");
}
session.addEventListener("end", onSessionEnd, false);
session.requestAnimationFrame(onXRFrame);
return eventPromise;
}
xr_session_promise_test('Make sure video.rVFC callbacks started during an immersive session continue after it ends',
testFunction, TRACKED_IMMERSIVE_DEVICE, 'immersive-vr');
</script>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment