Skip to content
101 changes: 82 additions & 19 deletions extensions/amp-video/0.1/test/test-video-cache.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {createElementWithAttributes} from '#core/dom';
import * as Preact from '#core/dom/jsx';

import {Services} from '#service';
import {installPerformanceService} from '#service/performance-impl';
Expand Down Expand Up @@ -41,7 +42,7 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl, env.ampdoc);

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'
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video1.mp4?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});

Expand All @@ -52,7 +53,7 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl, env.ampdoc);

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'
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video1.mp4?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});

Expand All @@ -66,7 +67,7 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl, env.ampdoc);

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

Expand All @@ -81,7 +82,7 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl, env.ampdoc);

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'
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video1.mp4?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});
});
Expand All @@ -94,41 +95,40 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl, env.ampdoc);

expect(xhrSpy).to.have.been.calledWith(
'https://website-com.cdn.ampproject.org/mbv/s/website.com/video.html?amp_video_host_url=https%3A%2F%2Fcanonical.com'
'https://website-com.cdn.ampproject.org/mbv/s/website.com/video.html?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});

it('should send the request to the correct address if the video has a relative url', async () => {
const videoEl = createVideo([{'src': 'video.html'}]);
it('should always add the ACAO header', async () => {
const videoEl = createVideo([{'src': 'https://website.com/video.html'}]);
const xhrSpy = env.sandbox.spy(xhrService, 'fetch');

await fetchCachedSources(videoEl, env.ampdoc);

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

it('should send the request to the correct address if the video has a .gif extension', async () => {
const videoEl = createVideo([{'src': 'https://website.com/video.gif'}]);
it('should send the request to the correct address if the video has a relative url', async () => {
const videoEl = createVideo([{'src': 'video.html'}]);
const xhrSpy = env.sandbox.spy(xhrService, 'fetch');

await fetchCachedSources(videoEl, env.ampdoc);

expect(xhrSpy).to.have.been.calledWith(
'https://website-com.cdn.ampproject.org/mbv/s/website.com/video.gif?amp_video_host_url=https%3A%2F%2Fcanonical.com'
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video.html?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});

it('should add the ACAO queryparam if the video is crossorigin', async () => {
const videoEl = createVideo([{'src': 'video.html'}]);
videoEl.setAttribute('crossorigin', '');
it('should send the request to the correct address if the video has a .gif extension', async () => {
const videoEl = createVideo([{'src': 'https://website.com/video.gif'}]);
const xhrSpy = env.sandbox.spy(xhrService, 'fetch');

await fetchCachedSources(videoEl, env.ampdoc);

expect(xhrSpy).to.have.been.calledWith(
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video.html?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
'https://website-com.cdn.ampproject.org/mbv/s/website.com/video.gif?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});
});
Expand Down Expand Up @@ -396,6 +396,14 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
expect(videoEl.querySelector('source[data-bitrate]')).to.not.be.null;
});

it('should set the crossorigin attribute to the video', async () => {
const videoEl = createVideo([{'src': 'video.html'}]);

await fetchCachedSources(videoEl, env.ampdoc);

expect(videoEl.hasAttribute('crossorigin')).to.be.true;
});

it('should set an attribute on cached video sources', async () => {
env.sandbox.stub(xhrService, 'fetch').resolves({
json: () =>
Expand Down Expand Up @@ -494,6 +502,61 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
});
});

describe('captions_src field', async () => {
it('should append track element if the cache responds with captions_src', async () => {
env.sandbox.stub(xhrService, 'fetch').resolves({
json: () =>
Promise.resolve({
'captions_src': 'captions_src_response.vtt',
'captions_srclang': 'en-us',
'sources': [
{'url': 'video.mp4', 'bitrate_kbps': 700, 'type': 'video/mp4'},
],
}),
});
const videoEl = createVideo([{src: 'video.mp4'}]);
await fetchCachedSources(videoEl, env.ampdoc);

const trackEl = videoEl.querySelector('track');
expect(trackEl).to.exist;
});
it('should not append track element if video already has a track child', async () => {
Comment thread
processprocess marked this conversation as resolved.
env.sandbox.stub(xhrService, 'fetch').resolves({
json: () =>
Promise.resolve({
'captions_src': 'captions_src_response.vtt',
'captions_srclang': 'en-us',
'sources': [
{'url': 'video.mp4', 'bitrate_kbps': 700, 'type': 'video/mp4'},
],
}),
});
const videoEl = createVideo([{src: 'video.mp4'}]);
videoEl.appendChild(<track />);
await fetchCachedSources(videoEl, env.ampdoc);

const trackEl = videoEl.querySelector(
'track[src="captions_src_response.vtt"]'
);
expect(trackEl).to.not.exist;
});
it('should not append track element if captions_src does not exist', async () => {
env.sandbox.stub(xhrService, 'fetch').resolves({
json: () =>
Promise.resolve({
'sources': [
{'url': 'video.mp4', 'bitrate_kbps': 700, 'type': 'video/mp4'},
],
}),
});
const videoEl = createVideo([{src: 'video.mp4'}]);
await fetchCachedSources(videoEl, env.ampdoc);

const trackEl = videoEl.querySelector('track');
expect(trackEl).to.not.exist;
});
});

describe('web stories: inlined video', async () => {
it('should use the inlined source for the first video in the story instead of sending an XHR request', async () => {
// Set up an inlined source response for the first video in the story
Expand Down Expand Up @@ -531,10 +594,10 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl3, env.ampdoc);

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

Expand All @@ -550,7 +613,7 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl, env.ampdoc);

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'
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video1.mp4?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});

Expand All @@ -571,7 +634,7 @@ describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
await fetchCachedSources(videoEl, env.ampdoc);

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'
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video1.mp4?amp_video_host_url=https%3A%2F%2Fcanonical.com&amp_video_require_acao_header=1'
);
});
});
Expand Down
32 changes: 29 additions & 3 deletions extensions/amp-video/0.1/video-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export function fetchCachedSources(
return Promise.resolve();
}

// Always set crossorigin attribute so captions can be set.
if (!videoEl.hasAttribute('crossorigin')) {
videoEl.setAttribute('crossorigin', '');
}

const videoSrc = videoEl.getAttribute('src');
const sourceSrc = videoEl.querySelector('source[src]')?.getAttribute('src');
if (!videoSrc && !sourceSrc) {
Expand All @@ -49,6 +54,7 @@ export function fetchCachedSources(
.then((response) => {
applySourcesToVideo(videoEl, response['sources'], maxBitrate);
applyAudioInfoToVideo(videoEl, response['has_audio']);
applyCaptionsTrackToVideo(videoEl, response);
})
.catch(() => {
// If cache fails, video should still load properly.
Expand Down Expand Up @@ -156,6 +162,28 @@ function applyAudioInfoToVideo(videoEl, hasAudio) {
}
}

/**
* Appends captions track to video if captions url is defined and video
* element doesn't have a track child specified in the document.
* @param {!Element} videoEl
* @param {!Object} response
*/
function applyCaptionsTrackToVideo(videoEl, response) {
if (
!response['captions_src'] ||
!response['captions_srclang'] ||
videoEl.querySelector('track')
) {
return;
}
const trackEl = createElementWithAttributes(videoEl.ownerDocument, 'track', {
'src': response['captions_src'],
'srclang': response['captions_srclang'],
'kind': 'captions',
});
videoEl.appendChild(trackEl);
}

/**
* If present, moves the src attribute to a source element to enable playing
* from multiple sources: the cached ones and the fallback initial src.
Expand Down Expand Up @@ -230,9 +258,7 @@ function requestCachedVideoSources(videoEl, ampdoc) {
const requestUrl = addParamsToUrl(cacheUrl.replace(/\/[ic]\//, '/mbv/'), {
'amp_video_host_url':
/* document url that contains the video */ canonicalUrl,
'amp_video_require_acao_header': videoEl.hasAttribute('crossorigin')
? 1
: null,
'amp_video_require_acao_header': 1,
});
return Services.xhrFor(win)
.fetch(requestUrl, {prerenderSafe: true})
Expand Down