mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-12 04:22:28 +00:00
LibWeb: Implement MediaCapabilities.decodingInfo()
This commit is contained in:
parent
3b577e6135
commit
0206697d36
Notes:
github-actions[bot]
2025-02-18 17:19:43 +00:00
Author: https://github.com/Psychpsyo
Commit: 0206697d36
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3548
Reviewed-by: https://github.com/ADKaster ✅
Reviewed-by: https://github.com/Lubrsi
Reviewed-by: https://github.com/shannonbooth
10 changed files with 1047 additions and 1 deletions
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<meta name="timeout" content="long">
|
||||
<script>
|
||||
self.GLOBAL = {
|
||||
isWindow: function() { return true; },
|
||||
isWorker: function() { return false; },
|
||||
isShadowRealm: function() { return false; },
|
||||
};
|
||||
</script>
|
||||
<script src="../resources/testharness.js"></script>
|
||||
<script src="../resources/testharnessreport.js"></script>
|
||||
|
||||
<div id=log></div>
|
||||
<script src="../media-capabilities/decodingInfo.any.js"></script>
|
|
@ -0,0 +1,508 @@
|
|||
// META: timeout=long
|
||||
'use strict';
|
||||
|
||||
// Minimal VideoConfiguration that will be allowed per spec. All optional
|
||||
// properties are missing.
|
||||
const minimalVideoConfiguration = {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
};
|
||||
|
||||
// Minimal AudioConfiguration that will be allowed per spec. All optional
|
||||
// properties are missing.
|
||||
const minimalAudioConfiguration = {
|
||||
contentType: 'audio/webm; codecs="opus"',
|
||||
};
|
||||
|
||||
// AudioConfiguration with optional spatialRendering param.
|
||||
const audioConfigurationWithSpatialRendering = {
|
||||
contentType: 'audio/webm; codecs="opus"',
|
||||
spatialRendering: true,
|
||||
};
|
||||
|
||||
// VideoConfiguration with optional hdrMetadataType, colorGamut, and
|
||||
// transferFunction properties.
|
||||
const videoConfigurationWithDynamicRange = {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08.00.09.16.09.00"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
hdrMetadataType: 'smpteSt2086',
|
||||
colorGamut: 'rec2020',
|
||||
transferFunction: 'pq',
|
||||
};
|
||||
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo());
|
||||
}, "Test that decodingInfo rejects if it doesn't get a configuration");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({}));
|
||||
}, "Test that decodingInfo rejects if the MediaConfiguration isn't valid");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
video: minimalVideoConfiguration,
|
||||
audio: minimalAudioConfiguration,
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the MediaConfiguration does not have a type");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the configuration doesn't have an audio or video field");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: -1,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration has a negative framerate");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 0,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration has a framerate set to 0");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: Infinity,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration has a framerate set to Infinity");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'fgeoa',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType doesn't parse");
|
||||
|
||||
// See https://mimesniff.spec.whatwg.org/#example-valid-mime-type-string
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm;',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType is not a valid MIME type string");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'audio/fgeoa',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType isn't of type video");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'application/ogg; codec=vorbis',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType is of type audio");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: {
|
||||
contentType: 'application/ogg; codec=theora',
|
||||
channels: 2,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType is of type video");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"; foo="bar"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType has more than one parameter");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; foo="bar"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType has one parameter that isn't codecs");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType does not imply a single media codec but has no codecs parameter");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08, vp8"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates multiple video codecs");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08, opus"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration contentType has a codecs parameter that indicates both an audio and a video codec");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: '24000/1001',
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects framerate in the form of x/y");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: '24000/0',
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects framerate in the form of x/0");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: '0/10001',
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects framerate in the form of 0/y");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: '-24000/10001',
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects framerate in the form of -x/y");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: '24000/-10001',
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects framerate in the form of x/-y");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: '24000/',
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects framerate in the form of x/");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: '1/3x',
|
||||
}
|
||||
}));
|
||||
}, "Test that decodingInfo rejects framerate with trailing unallowed characters");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'fgeoa' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType doesn't parse");
|
||||
|
||||
// See https://mimesniff.spec.whatwg.org/#example-valid-mime-type-string
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'audio/mpeg;' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType is not a valid MIME type string");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'video/fgeoa' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType isn't of type audio");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'audio/webm; codecs="opus"; foo="bar"' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType has more than one parameter");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'audio/webm; foo="bar"' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType has one parameter that isn't codecs");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'audio/webm' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType does not imply a single media codec but has no codecs parameter");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'audio/webm; codecs="vorbis, opus"' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates multiple audio codecs");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: { contentType: 'audio/webm; codecs="vp09.00.10.08, opus"' },
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the audio configuration contentType has a codecs parameter that indicates both an audio and a video codec");
|
||||
|
||||
promise_test(t => {
|
||||
return navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: minimalVideoConfiguration,
|
||||
audio: minimalAudioConfiguration,
|
||||
}).then(ability => {
|
||||
assert_equals(typeof ability.supported, "boolean");
|
||||
assert_equals(typeof ability.smooth, "boolean");
|
||||
assert_equals(typeof ability.powerEfficient, "boolean");
|
||||
assert_equals(typeof ability.keySystemAccess, "object");
|
||||
});
|
||||
}, "Test that decodingInfo returns a valid MediaCapabilitiesInfo objects");
|
||||
|
||||
async_test(t => {
|
||||
var validTypes = [ 'file', 'media-source' ];
|
||||
var invalidTypes = [ undefined, null, '', 'foobar', 'mse', 'MediaSource',
|
||||
'record', 'transmission' ];
|
||||
|
||||
var validPromises = [];
|
||||
var invalidCaught = 0;
|
||||
|
||||
validTypes.forEach(type => {
|
||||
validPromises.push(navigator.mediaCapabilities.decodingInfo({
|
||||
type: type,
|
||||
video: minimalVideoConfiguration,
|
||||
audio: minimalAudioConfiguration,
|
||||
}));
|
||||
});
|
||||
|
||||
// validTypes are tested via Promise.all(validPromises) because if one of the
|
||||
// promises fail, Promise.all() will reject. This mechanism can't be used for
|
||||
// invalid types which will be tested individually and increment invalidCaught
|
||||
// when rejected until the amount of rejection matches the expectation.
|
||||
Promise.all(validPromises).then(t.step_func(() => {
|
||||
for (var i = 0; i < invalidTypes.length; ++i) {
|
||||
navigator.mediaCapabilities.decodingInfo({
|
||||
type: invalidTypes[i],
|
||||
video: minimalVideoConfiguration,
|
||||
audio: minimalAudioConfiguration,
|
||||
}).then(t.unreached_func(), t.step_func(e => {
|
||||
assert_equals(e.name, 'TypeError');
|
||||
++invalidCaught;
|
||||
if (invalidCaught == invalidTypes.length)
|
||||
t.done();
|
||||
}));
|
||||
}
|
||||
}), t.unreached_func('Promise.all should not reject for valid types'));
|
||||
}, "Test that decodingInfo rejects if the MediaConfiguration does not have a valid type");
|
||||
|
||||
promise_test(t => {
|
||||
return navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
audio: audioConfigurationWithSpatialRendering,
|
||||
}).then(ability => {
|
||||
assert_equals(typeof ability.supported, "boolean");
|
||||
assert_equals(typeof ability.smooth, "boolean");
|
||||
assert_equals(typeof ability.powerEfficient, "boolean");
|
||||
assert_equals(typeof ability.keySystemAccess, "object");
|
||||
});
|
||||
}, "Test that decodingInfo with spatialRendering set returns a valid MediaCapabilitiesInfo objects");
|
||||
|
||||
promise_test(t => {
|
||||
return navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: videoConfigurationWithDynamicRange,
|
||||
}).then(ability => {
|
||||
assert_equals(typeof ability.supported, "boolean");
|
||||
assert_equals(typeof ability.smooth, "boolean");
|
||||
assert_equals(typeof ability.powerEfficient, "boolean");
|
||||
assert_equals(typeof ability.keySystemAccess, "object");
|
||||
});
|
||||
}, "Test that decodingInfo with hdrMetadataType, colorGamut, and transferFunction set returns a valid MediaCapabilitiesInfo objects");
|
||||
|
||||
promise_test(t => {
|
||||
// VP9 has a default color space of BT.709 in the codec string. So this will
|
||||
// mismatch against the provided colorGamut and transferFunction.
|
||||
let bt709Config = videoConfigurationWithDynamicRange;
|
||||
bt709Config.contentType = 'video/webm; codecs="vp09.00.10.08"';
|
||||
return navigator.mediaCapabilities
|
||||
.decodingInfo({
|
||||
type: 'file',
|
||||
video: bt709Config,
|
||||
})
|
||||
.then(ability => {
|
||||
assert_equals(typeof ability.supported, 'boolean');
|
||||
assert_equals(typeof ability.smooth, 'boolean');
|
||||
assert_equals(typeof ability.powerEfficient, 'boolean');
|
||||
assert_equals(typeof ability.keySystemAccess, 'object');
|
||||
assert_false(ability.supported);
|
||||
});
|
||||
}, 'Test that decodingInfo with mismatched codec color space is unsupported');
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
hdrMetadataType: ""
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration has an empty hdrMetadataType");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
colorGamut: true
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration has a colorGamut set to true");
|
||||
|
||||
promise_test(t => {
|
||||
return promise_rejects_js(t, TypeError, navigator.mediaCapabilities.decodingInfo({
|
||||
type: 'file',
|
||||
video: {
|
||||
contentType: 'video/webm; codecs="vp09.00.10.08"',
|
||||
width: 800,
|
||||
height: 600,
|
||||
bitrate: 3000,
|
||||
framerate: 24,
|
||||
transferFunction: 3
|
||||
},
|
||||
}));
|
||||
}, "Test that decodingInfo rejects if the video configuration has a transferFunction set to 3");
|
Loading…
Add table
Add a link
Reference in a new issue