Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow WebP and AVIF image streams #3856

Merged
merged 5 commits into from Jan 13, 2022
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
109 changes: 87 additions & 22 deletions lib/util/stream_utils.js
Expand Up @@ -406,7 +406,7 @@ shaka.util.StreamUtils = class {
shaka.util.StreamUtils.filterManifestByCurrentVariant(
currentVariant, manifest);
shaka.util.StreamUtils.filterTextStreams_(manifest);
shaka.util.StreamUtils.filterImageStreams_(manifest);
await shaka.util.StreamUtils.filterImageStreams_(manifest);
}


Expand Down Expand Up @@ -761,37 +761,61 @@ shaka.util.StreamUtils = class {
* @param {shaka.extern.Manifest} manifest
* @private
*/
static filterImageStreams_(manifest) {
// Filter image streams.
manifest.imageStreams = manifest.imageStreams.filter((stream) => {
// TODO: re-examine this and avoid allow-listing the MIME types we can
// accept.
const validMimeTypes = [
'image/svg+xml',
'image/png',
'image/jpeg',
];
const Platform = shaka.util.Platform;
// Add webp support to popular platforms that support it.
const webpSupport = Platform.isWebOS() ||
Platform.isTizen() ||
Platform.isChromecast();
if (webpSupport) {
validMimeTypes.push('image/webp');
static async filterImageStreams_(manifest) {
const imageStreams = [];
for (const stream of manifest.imageStreams) {
const mimeType = stream.mimeType;
if (!shaka.util.StreamUtils.supportedImageMimeTypes.has(mimeType) &&
!shaka.util.StreamUtils.unsupportedImageMimeTypes.has(mimeType)) {
const minImage = shaka.util.StreamUtils.minImage.get(mimeType);
if (minImage) {
// eslint-disable-next-line no-await-in-loop
const res = await shaka.util.StreamUtils.isImageSupported_(minImage);
if (res) {
shaka.util.StreamUtils.supportedImageMimeTypes.add(mimeType);
} else {
shaka.util.StreamUtils.unsupportedImageMimeTypes.add(mimeType);
}
} else {
shaka.util.StreamUtils.unsupportedImageMimeTypes.add(mimeType);
}
}
// TODO: add support to image/webp and image/avif
const keep = validMimeTypes.includes(stream.mimeType);

const keep = shaka.util.StreamUtils.supportedImageMimeTypes.has(mimeType);

if (!keep) {
shaka.log.debug('Dropping image stream. Is not supported by the ' +
'platform.', stream);
} else {
imageStreams.push(stream);
}
}
manifest.imageStreams = imageStreams;
}

return keep;
/**
* @param {string} minImage
* @return {!Promise.<boolean>}
* @private
*/
static isImageSupported_(minImage) {
return new Promise((resolve) => {
const imageElement = /** @type {HTMLImageElement} */(new Image());
imageElement.src = minImage;
if ('decode' in imageElement) {
imageElement.decode().then(() => {
joeyparrish marked this conversation as resolved.
Show resolved Hide resolved
resolve(true);
}).catch(() => {
resolve(false);
});
} else {
imageElement.onload = imageElement.onerror = () => {
resolve(imageElement.height === 2);
};
}
});
}


/**
* @param {shaka.extern.Stream} s0
* @param {shaka.extern.Stream} s1
Expand Down Expand Up @@ -1445,3 +1469,44 @@ shaka.util.StreamUtils.DecodingAttributes = {
POWER: 'powerEfficient',
BANDWIDTH: 'bandwidth',
};

/**
* @private {!Set.<string>}
*/
shaka.util.StreamUtils.supportedImageMimeTypes = new Set()
avelad marked this conversation as resolved.
Show resolved Hide resolved
.add('image/svg+xml')
.add('image/png')
.add('image/jpeg');

/**
* @private {!Set.<string>}
*/
shaka.util.StreamUtils.unsupportedImageMimeTypes = new Set();

/**
* @const {string}
* @private
*/
shaka.util.StreamUtils.minWebPImage = 'data:image/webp;base64,UklGRjoAAABXRU' +
'JQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwY' +
'AAA';

/**
* @const {string}
* @private
*/
shaka.util.StreamUtils.minAvifImage = 'data:image/avif;base64,AAAAIGZ0eXBhdm' +
Copy link
Member

Choose a reason for hiding this comment

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

I can't believe how large this tiny AVIF image is. I played around in https://squoosh.app/ and only managed to shave it down from 311 bytes to 305. :-(

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have not found a smaller one that works well :(

'lmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljd' +
'AAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEA' +
'AAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAA' +
'AamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAA' +
'xhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAA' +
'CVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=';

/**
* @const {!Map.<string, string>}
* @private
*/
shaka.util.StreamUtils.minImage = new Map()
avelad marked this conversation as resolved.
Show resolved Hide resolved
.set('image/webp', shaka.util.StreamUtils.minWebPImage)
.set('image/avif', shaka.util.StreamUtils.minAvifImage);