Skip to content

Commit

Permalink
Video cache for amp-video[src]. (#34570)
Browse files Browse the repository at this point in the history
* Video cache for amp-video[src].

* Update extensions/amp-video/0.1/video-cache.js

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>

* Update extensions/amp-video/0.1/video-cache.js

* Import iterateCursor.

* Reviews.

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
  • Loading branch information
gmajoulet and jridgewell authored May 27, 2021
1 parent 739d3ac commit 5dc1502
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 2 deletions.
65 changes: 65 additions & 0 deletions extensions/amp-video/0.1/test/test-video-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video2.mp4?amp_video_host_url=https%3A%2F%2Fcanonical.com'
);
});

it('should select the video[src] and never the sources children', async () => {
const videoEl = createVideo([
{src: 'video2.mp4'},
{src: 'video3.mp4', type: 'video/mp4'},
]);
videoEl.setAttribute('src', 'video1.mp4');
const xhrSpy = env.sandbox.spy(xhrService, 'fetch');

await fetchCachedSources(videoEl, env.win);

expect(xhrSpy).to.have.been.calledWith(
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video1.mp4?amp_video_host_url=https%3A%2F%2Fcanonical.com'
);
});
});

describe('url forming', () => {
Expand Down Expand Up @@ -145,6 +160,56 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
expect(addedSources[1].getAttribute('data-bitrate')).to.equal('1500');
expect(addedSources[2].getAttribute('data-bitrate')).to.equal('700');
});

it('should add video[src] as the last fallback source', async () => {
env.sandbox.stub(xhrService, 'fetch').resolves({
json: () =>
Promise.resolve({
sources: [
{'url': 'video1.mp4', 'bitrate_kbps': 700, type: 'video/mp4'},
{'url': 'video2.mp4', 'bitrate_kbps': 2000, type: 'video/mp4'},
{'url': 'video3.mp4', 'bitrate_kbps': 1500, type: 'video/mp4'},
],
}),
});

const videoEl = createVideo([{src: 'video.mp4'}]);
videoEl.setAttribute('src', 'video1.mp4');
videoEl.setAttribute('type', 'video/mp4');

await fetchCachedSources(videoEl, env.win);

const lastSource = videoEl.querySelector('source:last-of-type');
expect(lastSource.getAttribute('src')).to.equal('video1.mp4');
expect(lastSource.getAttribute('type')).to.equal('video/mp4');
});

it('should clear the unused sources when video[src]', async () => {
env.sandbox.stub(xhrService, 'fetch').resolves({
json: () =>
Promise.resolve({
sources: [
{'url': 'video1.mp4', 'bitrate_kbps': 700, type: 'video/mp4'},
{'url': 'video2.mp4', 'bitrate_kbps': 2000, type: 'video/mp4'},
{'url': 'video3.mp4', 'bitrate_kbps': 1500, type: 'video/mp4'},
],
}),
});

const videoEl = createVideo([
{src: 'video.mp4'},
{src: 'video.mp4'},
{src: 'video.mp4'},
{src: 'video.mp4'},
{src: 'video.mp4'},
]);
videoEl.setAttribute('src', 'video1.mp4');

await fetchCachedSources(videoEl, env.win);

const addedSources = videoEl.querySelectorAll('source');
expect(addedSources).to.have.lengthOf(4); // 3 from cache + 1 fallback.
});
});

describe('end to end', () => {
Expand Down
44 changes: 42 additions & 2 deletions extensions/amp-video/0.1/video-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@

import {Services} from '../../../src/services';
import {addParamsToUrl, resolveRelativeUrl} from '../../../src/url';
import {createElementWithAttributes, matches} from '../../../src/dom';
import {
createElementWithAttributes,
iterateCursor,
matches,
removeElement,
} from '../../../src/dom';
import {extensionScriptInNode} from '../../../src/service/extension-script';
import {toArray} from '../../../src/core/types/array';
import {user} from '../../../src/log';
Expand All @@ -33,13 +38,17 @@ import {user} from '../../../src/log';
export function fetchCachedSources(videoEl, win) {
if (
!extensionScriptInNode(win, 'amp-cache-url', '0.1') ||
!videoEl.querySelector('source[src]').getAttribute('src')
!(
videoEl.getAttribute('src') ||
videoEl.querySelector('source[src]')?.getAttribute('src')
)
) {
user().error('AMP-VIDEO', 'Video cache not properly configured');
return Promise.resolve();
}
const {canonicalUrl, sourceUrl} = Services.documentInfoForDoc(win.document);
const servicePromise = Services.cacheUrlServicePromiseForDoc(videoEl);
maybeReplaceSrcWithSourceElement(videoEl, win);
const videoUrl = resolveRelativeUrl(selectVideoSource(videoEl), sourceUrl);
return servicePromise
.then((service) => service.createCacheUrl(videoUrl))
Expand Down Expand Up @@ -95,3 +104,34 @@ function applySourcesToVideo(videoEl, sources) {
videoEl.insertBefore(sourceEl, videoEl.firstChild);
});
}

/**
* If present, moves the src attribute to a source element to enable playing
* from multiple sources: the cached ones and the fallback initial src.
* @param {!Element} videoEl
* @param {!Window} win
*/
function maybeReplaceSrcWithSourceElement(videoEl, win) {
if (!videoEl.hasAttribute('src')) {
return;
}
const sourceEl = win.document.createElement('source');
const srcAttr = videoEl.getAttribute('src');
sourceEl.setAttribute('src', srcAttr);

const typeAttr = videoEl.getAttribute('type');
if (typeAttr) {
sourceEl.setAttribute('type', typeAttr);
}

// Remove the src attr so the source children can play.
videoEl.removeAttribute('src');
videoEl.removeAttribute('type');

// Remove all existing sources as they are never supposed to play for a video
// that has a src, cf https://html.spec.whatwg.org/#concept-media-load-algorithm
const sourceEls = videoEl.querySelectorAll('source');
iterateCursor(sourceEls, (el) => removeElement(el));

videoEl.insertBefore(sourceEl, videoEl.firstChild);
}

0 comments on commit 5dc1502

Please sign in to comment.