importScripts("mediasource-worker-util.js");
// Note, we do not use testharness.js utilities within the worker context
// because it also communicates using postMessage to the main HTML document's
// harness, and would confuse the test case message parsing there.
let util = new MediaSourceWorkerUtil();
let sourceBuffer;
// Phases of this test case, in sequence:
const testPhase = {
// Main thread verifies initial unattached HTMLMediaElement duration is NaN
// and readyState is HAVE_NOTHING, then starts this worker.
// This worker creates a MediaSource, verifies its initial duration
// is NaN, creates an object URL for the MediaSource and sends the URL to the
// main thread.
kInitial: "Initial",
// Main thread receives MediaSourceHandle, re-verifies that the media element
// duration is still NaN and readyState is still HAVE_NOTHING, and then sets
// the handle as the srcObject of the media element, eventually causing worker
// mediaSource 'onsourceopen' event dispatch.
kAttaching: "Awaiting sourceopen event that signals attachment is setup",
kRequestNaNDurationCheck:
"Sending request to main thread to verify expected duration of the freshly setup attachment",
kConfirmNaNDurationResult:
"Checking that main thread correctly ACK'ed the freshly setup attachment's duration verification request",
kRequestHaveNothingReadyStateCheck:
"Sending request to main thread to verify expected readyState of HAVE_NOTHING of the freshly setup attachment",
kConfirmHaveNothingReadyStateResult:
"Checking that main thread correctly ACK'ed the freshly setup attachment's readyState HAVE_NOTHING verification request",
kRequestSetDurationCheck:
"Sending request to main thread to verify explicitly set duration before any media data has been appended",
kConfirmSetDurationResult:
"Checking that main thread correctly ACK'ed the duration verification request of explicitly set duration before any media data has been appended",
kRequestHaveNothingReadyStateRecheck:
"Sending request to main thread to recheck that the readyState is still HAVE_NOTHING",
kConfirmHaveNothingReadyStateRecheckResult:
"Checking that main thread correctly ACK'ed the request to recheck readyState of HAVE_NOTHING",
kRequestAwaitNewDurationCheck:
"Buffering media and then sending request to main thread to await duration reaching the expected value due to buffering",
kConfirmAwaitNewDurationResult:
"Checking that main thread correctly ACK'ed the request to await duration reaching the expected value due to buffering",
kRequestAtLeastHaveMetadataReadyStateCheck:
"Sending request to main thread to verify expected readyState of at least HAVE_METADATA due to buffering",
kConfirmAtLeastHaveMetadataReadyStateResult:
"Checking that main thread correctly ACK'ed the request to verify expected readyState of at least HAVE_METADATA due to buffering",
};
let phase = testPhase.kInitial;
// Setup handler for receipt of attachment completion.
util.mediaSource.addEventListener("sourceopen", () => {
assert(phase === testPhase.kAttaching, "Unexpected sourceopen received by Worker mediaSource.");
phase = testPhase.kRequestNaNDurationCheck;
processPhase();
}, { once : true });
// Setup handler for receipt of acknowledgement of successful verifications from
// main thread. |ackVerificationData| contains the round-tripped verification
// request that the main thread just sent, and is used in processPhase to ensure
// the ACK for this phase matched the request for verification.
let ackVerificationData;
onmessage = e => {
if (e.data === undefined || e.data.subject !== messageSubject.ACK_VERIFIED || e.data.info === undefined) {
postMessage({
subject: messageSubject.ERROR,
info: "Invalid message received by Worker"
});
return;
}
ackVerificationData = e.data.info;
processPhase(/* isResponseToAck */ true);
};
processPhase();
// Returns true if checks succeed, false otherwise.
function checkAckVerificationData(expectedRequest) {
// Compares only subject and info fields. Uses logic similar to testharness.js's
// same_value(x,y) to correctly handle NaN, but doesn't distinguish +0 from -0.
function messageValuesEqual(m1, m2) {
if (m1.subject !== m1.subject) {
// NaN case
if (m2.subject === m2.subject)
return false;
} else if (m1.subject !== m2.subject) {
return false;
}
if (m1.info !== m1.info) {
// NaN case
return (m2.info !== m2.info);
}
return m1.info === m2.info;
}
if (messageValuesEqual(expectedRequest, ackVerificationData)) {
ackVerificationData = undefined;
return true;
}
postMessage({
subject: messageSubject.ERROR,
info: "ACK_VERIFIED message from main thread was for a mismatching request for this phase. phase=[" + phase +
"], expected request that would produce ACK in this phase=[" + JSON.stringify(expectedRequest) +
"], actual request reported with the ACK=[" + JSON.stringify(ackVerificationData) + "]"
});
ackVerificationData = undefined;
return false;
}
function bufferMediaAndSendDurationVerificationRequest() {
sourceBuffer = util.mediaSource.addSourceBuffer(util.mediaMetadata.type);
sourceBuffer.onerror = (err) => {
postMessage({ subject: messageSubject.ERROR, info: err });
};
sourceBuffer.onupdateend = () => {
// Sanity check the duration.
// Unnecessary for this buffering, except helps with test coverage.
var duration = util.mediaSource.duration;
if (isNaN(duration) || duration <= 0.0) {
postMessage({
subject: messageSubject.ERROR,
info: "mediaSource.duration " + duration + " is not within expected range (0,1)"
});
return;
}
// Await the main thread media element duration matching the worker
// mediaSource duration.
postMessage(getAwaitCurrentDurationRequest());
};
util.mediaLoadPromise.then(mediaData => { sourceBuffer.appendBuffer(mediaData); },
err => { postMessage({ subject: messageSubject.ERROR, info: err }) });
}
function getAwaitCurrentDurationRequest() {
// Sanity check that we have a numeric duration value now.
const dur = util.mediaSource.duration;
assert(!Number.isNaN(dur), "Unexpected NaN duration in worker");
return { subject: messageSubject.AWAIT_DURATION, info: dur };
}
function assert(conditionBool, description) {
if (conditionBool !== true) {
postMessage({
subject: messageSubject.ERROR,
info: "Current test phase [" + phase + "] failed worker assertion. " + description
});
}
}
function processPhase(isResponseToAck = false) {
assert(!isResponseToAck || (phase !== testPhase.kInitial && phase !== testPhase.kAttaching),
"Phase does not expect verification ack receipt from main thread");
// Some static request messages useful in transmission and ACK verification.
const nanDurationCheckRequest = { subject: messageSubject.VERIFY_DURATION, info: NaN };
const haveNothingReadyStateCheckRequest = { subject: messageSubject.VERIFY_HAVE_NOTHING };
const setDurationCheckRequest = { subject: messageSubject.AWAIT_DURATION, info: 0.1 };
const atLeastHaveMetadataReadyStateCheckRequest = { subject: messageSubject.VERIFY_AT_LEAST_HAVE_METADATA };
switch (phase) {
case testPhase.kInitial:
assert(Number.isNaN(util.mediaSource.duration), "Initial unattached MediaSource duration must be NaN, but instead is " + util.mediaSource.duration);
phase = testPhase.kAttaching;
let handle = util.mediaSource.handle;
postMessage({ subject: messageSubject.HANDLE, info: handle }, { transfer: [handle] } );
break;
case testPhase.kAttaching:
postMessage({
subject: messageSubject.ERROR,
info: "kAttaching phase is handled by main thread and by worker onsourceopen, not this switch case."
});
break;
case testPhase.kRequestNaNDurationCheck:
assert(!isResponseToAck);
postMessage(nanDurationCheckRequest);
phase = testPhase.kConfirmNaNDurationResult;
break;
case testPhase.kConfirmNaNDurationResult:
assert(isResponseToAck);
if (checkAckVerificationData(nanDurationCheckRequest)) {
phase = testPhase.kRequestHaveNothingReadyStateCheck;
processPhase();
}
break;
case testPhase.kRequestHaveNothingReadyStateCheck:
assert(!isResponseToAck);
postMessage(haveNothingReadyStateCheckRequest);
phase = testPhase.kConfirmHaveNothingReadyStateResult;
break;
case testPhase.kConfirmHaveNothingReadyStateResult:
assert(isResponseToAck);
if (checkAckVerificationData(haveNothingReadyStateCheckRequest)) {
phase = testPhase.kRequestSetDurationCheck;
processPhase();
}
break;
case testPhase.kRequestSetDurationCheck:
assert(!isResponseToAck);
const newDuration = setDurationCheckRequest.info;
assert(!Number.isNaN(newDuration) && newDuration > 0);
// Set the duration, then request verification.
util.mediaSource.duration = newDuration;
postMessage(setDurationCheckRequest);
phase = testPhase.kConfirmSetDurationResult;
break;
case testPhase.kConfirmSetDurationResult:
assert(isResponseToAck);
if (checkAckVerificationData(setDurationCheckRequest)) {
phase = testPhase.kRequestHaveNothingReadyStateRecheck;
processPhase();
}
break;
case testPhase.kRequestHaveNothingReadyStateRecheck:
assert(!isResponseToAck);
postMessage(haveNothingReadyStateCheckRequest);
phase = testPhase.kConfirmHaveNothingReadyStateRecheckResult;
break;
case testPhase.kConfirmHaveNothingReadyStateRecheckResult:
assert(isResponseToAck);
if (checkAckVerificationData(haveNothingReadyStateCheckRequest)) {
phase = testPhase.kRequestAwaitNewDurationCheck;
processPhase();
}
break;
case testPhase.kRequestAwaitNewDurationCheck:
assert(!isResponseToAck);
bufferMediaAndSendDurationVerificationRequest();
phase = testPhase.kConfirmAwaitNewDurationResult;
break;
case testPhase.kConfirmAwaitNewDurationResult:
assert(isResponseToAck);
if (checkAckVerificationData(getAwaitCurrentDurationRequest())) {
phase = testPhase.kRequestAtLeastHaveMetadataReadyStateCheck;
processPhase();
}
break;
case testPhase.kRequestAtLeastHaveMetadataReadyStateCheck:
assert(!isResponseToAck);
postMessage(atLeastHaveMetadataReadyStateCheckRequest);
phase = testPhase.kConfirmAtLeastHaveMetadataReadyStateResult;
break;
case testPhase.kConfirmAtLeastHaveMetadataReadyStateResult:
assert(isResponseToAck);
if (checkAckVerificationData(atLeastHaveMetadataReadyStateCheckRequest)) {
postMessage({ subject: messageSubject.WORKER_DONE });
}
phase = "No further phase processing should occur once WORKER_DONE message has been sent";
break;
default:
postMessage({
subject: messageSubject.ERROR,
info: "Unexpected test phase in worker:" + phase,
});
}
}