Skip to content

Commit

Permalink
✨ [Story Video] Connect CacheUrl service to amp-video and load sources (
Browse files Browse the repository at this point in the history
#33466)

* Added canonical to videos example

* Started video cache

* Use cdn url from url-impl

* Remove unnecessary | null

* Undo videos.html

* Remove console.log

* Using ternary operator for reduce

* Account for cached docs

* Update logic with toolbox

* Added service

* Undo amp-video

* Added cache url service

* Added owners

* Cache url to video

* Simplified cache example

* Updated title

* Changed name to amp-cache-url of folder

* Update readme

* Update comments

* Remove example

* Updated readme

* Added code

* Fixed tests

* Removed comment

* Added some more tests

* Fixed cdnurl

* Update names of docSupports

* Fix cacheUrl not replacing

* updated cacheDomain param name

* Check not cached doc to add cache sources

* improved implementation from comments

* added tests

* Added gregable file on examples

* Added warning

* Uppercase user error

* Updated opt in

* Fix response.json is promise

* Updated test

* Fixed toArray import

* Fixed visibilityState

* Use helper functions

* Added tests to amp-cache-url

* Update extensions/amp-cache-url/0.1/test/test-amp-cache-url.js

Co-authored-by: Ryan Cebulko <ryan@cebulko.com>

* Using extensionScriptInNode with win

* Updated tests to match comments

* Not export applySources

Co-authored-by: Gabriel Majoulet <gmajoulet@google.com>

Co-authored-by: Ryan Cebulko <ryan@cebulko.com>
Co-authored-by: Gabriel Majoulet <gmajoulet@google.com>
  • Loading branch information
3 people authored Apr 21, 2021
1 parent 0c4e5aa commit efd8b56
Show file tree
Hide file tree
Showing 10 changed files with 438 additions and 18 deletions.
2 changes: 2 additions & 0 deletions build-system/test-configs/dep-check-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ exports.rules = [
'src/service/extension-script.js',
'extensions/amp-video/0.1/amp-video.js->' +
'src/service/video-manager-impl.js',
'extensions/amp-video/0.1/video-cache.js->' +
'src/service/extension-script.js',
'extensions/amp-video-iframe/0.1/amp-video-iframe.js->' +
'src/service/video-manager-impl.js',
'extensions/amp-ooyala-player/0.1/amp-ooyala-player.js->' +
Expand Down
76 changes: 76 additions & 0 deletions examples/amp-story/videos-google-cache.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="canonical" href="https://amp.dev/hello/ampconf.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<title>Example story with Google cache videos</title>

<script async custom-element="amp-story" src="https://cdn.ampproject.org/v0/amp-story-1.0.js"></script>
<script async custom-element="amp-video" src="https://cdn.ampproject.org/v0/amp-video-0.1.js"></script>
<script async custom-element="amp-cache-url" src="https://cdn.ampproject.org/v0/amp-cache-url-0.1.js"></script>

<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
<style amp-custom>
.page-num {
display: block;
position: absolute;
left: 30px;
top: 30px;
width: 30px;
text-align: center;
font-family: sans-serif;
background-color: white;
border-radius: 50%;
}
</style>
</head>
<body>
<amp-story standalone id="cats"
title="Key Highlights of AMP Conf 2018" publisher="The AMP team">

<amp-story-page id="page-1">
<amp-story-grid-layer template="fill">
<amp-video autoplay loop cache="google"
id="video1"
width="400"
height="750"
poster="img-city1.jpeg#1"
layout="fill">
<source src="./video/stamp.mp4" type="video/mp4">
</amp-video>
</amp-story-grid-layer>
<amp-story-grid-layer><span class="page-num">1</span></amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-2">
<amp-story-grid-layer template="fill">
<amp-video autoplay loop cache="google"
id="video2"
width="400"
height="750"
poster="img-city1.jpeg#2"
layout="fill">
<source src="./video/stamp-animation.mp4" type="video/mp4">
</amp-video>
</amp-story-grid-layer>
<amp-story-grid-layer><span class="page-num">2</span></amp-story-grid-layer>
</amp-story-page>

<amp-story-page id="page-3">
<amp-story-grid-layer template="fill">
<amp-video autoplay loop cache="google"
id="video2"
width="400"
height="750"
poster="img-city1.jpeg#2"
layout="fill">
<source src="https://gregable.com/re2ae3Ei/sample-mp4-file.mp4?amp_video_host_url=gregable.com" type="video/mp4">
</amp-video>
</amp-story-grid-layer>
<amp-story-grid-layer><span class="page-num">2</span></amp-story-grid-layer>
</amp-story-page>
</amp-story>
</body>
</html>
16 changes: 16 additions & 0 deletions extensions/amp-cache-url/0.1/amp-cache-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,27 @@
* limitations under the License.
*/

import * as ampToolboxCacheUrl from '@ampproject/toolbox-cache-url';
import {urls} from '../../../src/config';

export class AmpCacheUrlService {
/**
* Create cache url service
*/
constructor() {}

/**
*
* @param {string} url
* @param {string=} cacheDomain the cache domain name (eg: cdn.approject.org)
* @return {!Promise<string>}
*/
createCacheUrl(url, cacheDomain = urls.cdn) {
return ampToolboxCacheUrl.createCacheUrl(
cacheDomain.replace(/https?:\/\//, ''),
url
);
}
}

AMP.extension('amp-cache-url', '0.1', (AMP) => {
Expand Down
28 changes: 28 additions & 0 deletions extensions/amp-cache-url/0.1/test/test-amp-cache-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {AmpCacheUrlService} from '../amp-cache-url';

describes.fakeWin(
'amp-cache-url',
{amp: {extensions: ['amp-cache-url']}},
() => {
it('should return a cached url', async () => {
const cacheUrlService = new AmpCacheUrlService();
const result = await cacheUrlService.createCacheUrl(
'https://amp.dev/stories'
);
expect(result).to.equal(
'https://amp-dev.cdn.ampproject.org/c/s/amp.dev/stories'
);
});

it('should not throw with empty url', async () => {
const cacheUrlService = new AmpCacheUrlService();
expect(cacheUrlService.createCacheUrl('')).to.not.be.rejected;
});

it('should not throw with invalid url', async () => {
const cacheUrlService = new AmpCacheUrlService();
expect(cacheUrlService.createCacheUrl('invalid url')).to.not.be.rejected;
});
}
);
9 changes: 8 additions & 1 deletion extensions/amp-video/0.1/amp-video.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
} from '../../../src/dom';
import {descendsFromStory} from '../../../src/utils/story';
import {dev, devAssert, user} from '../../../src/log';
import {fetchCachedSources} from './video-cache';
import {getBitrateManager} from './flexible-bitrate';
import {getMode} from '../../../src/mode';
import {htmlFor} from '../../../src/static-template';
Expand Down Expand Up @@ -259,11 +260,17 @@ export class AmpVideo extends AMP.BaseElement {
// Cached so mediapool operations (eg: swapping sources) don't interfere with this bool.
this.hasBitrateSources_ =
!!this.element.querySelector('source[data-bitrate]') ||
this.hasAnyCachedSources_();
this.hasAnyCachedSources_() ||
this.element.hasAttribute('cache');

installVideoManagerForDoc(element);

Services.videoManagerForDoc(element).register(this);

// Fetch and add cached sources URLs if opted-in, and if the sources don't already contained cached URLs from the AMP Cache.
if (this.element.hasAttribute('cache') && !this.hasAnyCachedSources_()) {
return fetchCachedSources(this.element, this.win);
}
}

/**
Expand Down
190 changes: 190 additions & 0 deletions extensions/amp-video/0.1/test/test-video-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* Copyright 2021 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {AmpCacheUrlService} from '../../../amp-cache-url/0.1/amp-cache-url';
import {Services} from '../../../../src/services';
import {createElementWithAttributes} from '../../../../src/dom';
import {createExtensionScript} from '../../../../src/service/extension-script';
import {fetchCachedSources} from '../video-cache';
import {xhrServiceForTesting} from '../../../../src/service/xhr-impl';

describes.realWin('amp-video cached-sources', {amp: true}, (env) => {
let xhrService;
let cacheUrlService;

beforeEach(() => {
xhrService = xhrServiceForTesting(env.win);
env.sandbox.stub(Services, 'xhrFor').returns(xhrService);

cacheUrlService = new AmpCacheUrlService();
env.sandbox
.stub(Services, 'cacheUrlServicePromiseForDoc')
.resolves(cacheUrlService);
env.win.document.head.appendChild(
createExtensionScript(env.win, 'amp-cache-url', '0.1')
);
env.sandbox
.stub(Services, 'documentInfoForDoc')
.returns({sourceUrl: 'https://example.com'});
});

describe('select sources', () => {
it('should select the source if there is only one source', async () => {
const videoEl = createVideo([{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'
);
});

it('should select the first source if there are similar sources', async () => {
const videoEl = createVideo([{src: 'video1.mp4'}, {src: 'video2.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'
);
});

it('should select the mp4 source if there are many sources', async () => {
const videoEl = createVideo([
{src: 'video1.mp4'},
{src: 'video2.mp4', type: 'video/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/video2.mp4'
);
});
});

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

await fetchCachedSources(videoEl, env.win);

expect(xhrSpy).to.have.been.calledWith(
'https://website-com.cdn.ampproject.org/mbv/s/website.com/video.html'
);
});

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.win);

expect(xhrSpy).to.have.been.calledWith(
'https://example-com.cdn.ampproject.org/mbv/s/example.com/video.html'
);
});
});

describe('add sources', () => {
it('should set the correct attributes on the source added', async () => {
env.sandbox.stub(xhrService, 'fetch').resolves({
json: () =>
Promise.resolve({
sources: [
{'url': 'video1.mp4', 'bitrate_kbps': 700, type: 'video/mp4'},
],
}),
});

const videoEl = createVideo([{src: 'video.mp4'}]);
await fetchCachedSources(videoEl, env.win);

const addedSource = videoEl.querySelector('source');
expect(addedSource.getAttribute('src')).to.equal('video1.mp4');
expect(addedSource.getAttribute('data-bitrate')).to.equal('700');
expect(addedSource.getAttribute('type')).to.equal('video/mp4');
});

it('should add the sources sorted by bitrate', 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'}]);

await fetchCachedSources(videoEl, env.win);

const addedSources = videoEl.querySelectorAll('source');
expect(addedSources[0].getAttribute('data-bitrate')).to.equal('2000');
expect(addedSources[1].getAttribute('data-bitrate')).to.equal('1500');
expect(addedSources[2].getAttribute('data-bitrate')).to.equal('700');
});
});

describe('end to end', () => {
it('should create the sources from the request with the correct attributes', 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.win);

expect(videoEl.querySelector('source[data-bitrate]')).to.not.be.null;
});
it('should not create the sources if there is amp-orig-src attribute', async () => {
const videoEl = createVideo([{'src': 'video.mp4', 'amp-orig-src': ''}]);
await fetchCachedSources(videoEl, env.win);

expect(videoEl.querySelector('source[data-bitrate]')).to.be.null;
});
});

function createVideo(children) {
const videoEl = createElementWithAttributes(env.win.document, 'amp-video', {
'cache': 'google',
'layout': 'fill',
});
children.forEach((childJson) => {
const sourceEl = createElementWithAttributes(
env.win.document,
'source',
childJson
);
videoEl.appendChild(sourceEl);
});
env.win.document.body.appendChild(videoEl);
return videoEl;
}
});
Loading

0 comments on commit efd8b56

Please sign in to comment.