diff --git a/ads/README.md b/ads/README.md index f0969ef945968..4df766cdb2c0b 100644 --- a/ads/README.md +++ b/ads/README.md @@ -216,6 +216,7 @@ Access to a publishers 1st party cookies may be achieved through a custom ad boo If the publisher would like to add custom JavaScript in the `remote.html` file that wants to read or write to the publisher owned cookies, then the publisher needs to ensure that the `remote.html` file is hosted on a sub-domain of the publisher URL. e.g. if the publisher hosts a webpage on https://nytimes.com, then the remote file should be hosted on something similar to https://sub-domain.nytimes.com for the custom JavaScript to have the abiity to read or write cookies for nytimes.com. ## Developer guidelines for a pull request + Please read through [DEVELOPING.md](../contributing/DEVELOPING.md) before contributing to this code repository. ### Files to change diff --git a/build-system/dep-check-config.js b/build-system/dep-check-config.js index 188ed725750af..1ef4fb4765bad 100644 --- a/build-system/dep-check-config.js +++ b/build-system/dep-check-config.js @@ -157,6 +157,8 @@ exports.rules = [ 'extensions/amp-youtube/0.1/amp-youtube.js->' + 'src/service/video-manager-impl.js', 'extensions/amp-a4a/0.1/amp-a4a.js->src/service/variable-source.js', + 'extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js->' + + 'src/service/video-manager-impl.js', 'extensions/amp-fx-parallax/0.1/amp-fx-parallax.js->' + 'src/service/parallax-impl.js', ], diff --git a/extensions/amp-bind/0.1/bind-impl.js b/extensions/amp-bind/0.1/bind-impl.js index f0e6eb66bf907..7787804df0083 100644 --- a/extensions/amp-bind/0.1/bind-impl.js +++ b/extensions/amp-bind/0.1/bind-impl.js @@ -797,7 +797,6 @@ export class Bind { return false; } - /** * Wait for bind scan to finish for testing. * diff --git a/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js b/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js new file mode 100644 index 0000000000000..b0d4fd583c70d --- /dev/null +++ b/extensions/amp-nexxtv-player/0.1/amp-nexxtv-player.js @@ -0,0 +1,229 @@ +/** + * Copyright 2017 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 {isLayoutSizeDefined} from '../../../src/layout'; +import {user} from '../../../src/log'; +import { + installVideoManagerForDoc, +} from '../../../src/service/video-manager-impl'; +import {removeElement} from '../../../src/dom'; +import {isObject} from '../../../src/types'; +import {tryParseJson} from '../../../src/json'; +import {listen} from '../../../src/event-helper'; +import {VideoEvents} from '../../../src/video-interface'; +import {videoManagerForDoc} from '../../../src/video-manager'; + +/** + * @implements {../../../src/video-interface.VideoInterface} + */ +class AmpNexxtvPlayer extends AMP.BaseElement { + + /** @param {!AmpElement} element */ + constructor(element) { + super(element); + + /** @private {?Element} */ + this.iframe_ = null; + + /** @private {?string} */ + this.videoIframeSrc_ = null; + + /** @private {?Function} */ + this.unlistenMessage_ = null; + + /** @private {?Promise} */ + this.playerReadyPromise_ = null; + + /** @private {?Function} */ + this.playerReadyResolver_ = null; + } + + /** + * @param {boolean=} opt_onLayout + * @override + */ + preconnectCallback(opt_onLayout) { + this.preconnect.url(this.getVideoIframeSrc_(), opt_onLayout); + } + + /** @override */ + isLayoutSupported(layout) { + return isLayoutSizeDefined(layout); + } + + /** @override */ + buildCallback() { + this.playerReadyPromise_ = new Promise(resolve => { + this.playerReadyResolver_ = resolve; + }); + + const iframe = this.element.ownerDocument.createElement('iframe'); + this.iframe_ = iframe; + + this.applyFillContent(iframe); + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('allowfullscreen', 'true'); + + this.element.appendChild(iframe); + + installVideoManagerForDoc(this.element); + videoManagerForDoc(this.element).register(this); + } + + getVideoIframeSrc_() { + if (this.videoIframeSrc_) { + return this.videoIframeSrc_; + } + + const mediaId = user().assert( + this.element.getAttribute('data-mediaid'), + 'The data-mediaid attribute is required for %s', + this.element); + + const client = user().assert(this.element.getAttribute('data-client'), + 'The data-client attribute is required for %s', + this.element); + + const start = this.element.getAttribute('data-seek-to') || 0; + const mode = this.element.getAttribute('data-mode') || 'static'; + const streamtype = this.element.getAttribute('data-streamtype') || 'video'; + const origin = this.element.getAttribute('data-origin') + || 'https://embed.nexx.cloud/'; + + let src = ''; + src += origin; + + if (streamtype !== 'video') { + src += `${encodeURIComponent(streamtype)}/`; + } + + src += `${encodeURIComponent(client)}/`; + src += `${encodeURIComponent(mediaId)}`; + src += `?start=${encodeURIComponent(String(start))}`; + src += `&datamode=${encodeURIComponent(mode)}&=1`; + + return this.videoIframeSrc_ = src; + } + + /** @override */ + viewportCallback(visible) { + this.element.dispatchCustomEvent(VideoEvents.VISIBILITY, {visible}); + } + + /** @override */ + layoutCallback() { + this.iframe_.src = this.getVideoIframeSrc_(); + + this.unlistenMessage_ = listen(this.iframe_,'message', event => { + this.handleNexxMessages_(event); + }); + + return this.loadPromise(this.iframe_) + .then(() => { + this.element.dispatchCustomEvent(VideoEvents.LOAD); + this.playerReadyResolver_(this.iframe_); + }); + } + + pauseCallback() { + if (this.iframe_) { + this.pause(); + } + } + + /** @override */ + unlayoutCallback() { + if (this.iframe_) { + removeElement(this.iframe_); + this.iframe_ = null; + } + + if (this.unlistenMessage_) { + this.unlistenMessage_(); + } + + return true; + } + + sendCommand_(command) { + this.iframe_.contentWindow./*OK*/postMessage(JSON.stringify({ + 'cmd': command, + }), '*'); + }; + + // emitter + handleNexxMessages_(event) { + const data = isObject(event.data) ? event.data : tryParseJson(event.data); + if (data === undefined) { + return; // We only process valid JSON. + } + + if (data.cmd == 'onload') { + this.element.dispatchCustomEvent(VideoEvents.LOAD); + this.playerReadyResolver_(this.iframe_); + } else if (data.cmd == 'play') { + this.element.dispatchCustomEvent(VideoEvents.PLAY); + } else if (data.cmd == 'pause') { + this.element.dispatchCustomEvent(VideoEvents.PAUSE); + } else if (data.cmd == 'mute') { + this.element.dispatchCustomEvent(VideoEvents.MUTED); + } else if (data.cmd == 'unmute') { + this.element.dispatchCustomEvent(VideoEvents.UNMUTED); + } + } + + // VideoInterface Implementation + // only send in json format + play() { + this.playerReadyPromise_.then(() => { + this.sendCommand_('play'); + }); + } + + pause() { + this.playerReadyPromise_.then(() => { + this.sendCommand_('pause'); + }); + } + + mute() { + this.playerReadyPromise_.then(() => { + this.sendCommand_('mute'); + }); + } + + unmute() { + this.playerReadyPromise_.then(() => { + this.sendCommand_('unmute'); + }); + } + + supportsPlatform() { + return true; + } + + isInteractive() { + return true; + } + + showControls() { + } + + hideControls() { + } +} + +AMP.registerElement('amp-nexxtv-player', AmpNexxtvPlayer); diff --git a/extensions/amp-nexxtv-player/0.1/test/test-amp-nexxtv-player.js b/extensions/amp-nexxtv-player/0.1/test/test-amp-nexxtv-player.js new file mode 100644 index 0000000000000..d6958af9da7ca --- /dev/null +++ b/extensions/amp-nexxtv-player/0.1/test/test-amp-nexxtv-player.js @@ -0,0 +1,128 @@ +/** + * Copyright 2017 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 { + createIframePromise, + doNotLoadExternalResourcesInTest, +} from '../../../../testing/iframe'; +import '../amp-nexxtv-player'; +import {listenOncePromise} from '../../../../src/event-helper'; +import {adopt} from '../../../../src/runtime'; +import {timerFor} from '../../../../src/timer'; +import {VideoEvents} from '../../../../src/video-interface'; +import * as sinon from 'sinon'; + +adopt(window); + +describe('amp-nexxtv-player', () => { + + let sandbox; + const timer = timerFor(window); + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + function getNexxtv(mediaid, client) { + return createIframePromise(true).then(iframe => { + doNotLoadExternalResourcesInTest(iframe.win); + const nexxtv = iframe.doc.createElement('amp-nexxtv-player'); + + if (mediaid) { + nexxtv.setAttribute('data-mediaid', mediaid); + } + if (client) { + nexxtv.setAttribute('data-client', client); + } + + // see yt test implementation + timer.promise(50).then(() => { + const nexxTimerIframe = nexxtv.querySelector('iframe'); + + nexxtv.implementation_.handleNexxMessages_({ + origin: 'https://embed.nexx.cloud', + source: nexxTimerIframe.contentWindow, + data: JSON.stringify({cmd: 'onload'}), + }); + }); + + return iframe.addElement(nexxtv); + }); + } + + it('renders nexxtv video player', () => { + return getNexxtv('PTPFEC4U184674', '583').then(nexxtv => { + const playerIframe = nexxtv.querySelector('iframe'); + + expect(playerIframe).to.not.be.null; + expect(playerIframe.src).to.equal('https://embed.nexx.cloud/583/' + + 'PTPFEC4U184674?start=0&datamode=static&=1'); + }); + }); + + it('fails without mediaid', () => { + return getNexxtv(null, '583').should.eventually.be.rejectedWith( + /The data-mediaid attribute is required/); + }); + + it('fails without client', () => { + return getNexxtv('PTPFEC4U184674', null).should.eventually.be.rejectedWith( + /The data-client attribute is required/); + }); + + + it('should forward events from nexxtv-player to the amp element', () => { + return getNexxtv('PTPFEC4U184674', '583').then(nexxtv => { + const iframe = nexxtv.querySelector('iframe'); + + return Promise.resolve() + .then(() => { + const p = listenOncePromise(nexxtv, VideoEvents.PLAY); + sendFakeMessage(nexxtv, iframe, 'play'); + return p; + }) + .then(() => { + const p = listenOncePromise(nexxtv, VideoEvents.MUTED); + sendFakeMessage(nexxtv, iframe, 'mute'); + return p; + }) + .then(() => { + const p = listenOncePromise(nexxtv, VideoEvents.PAUSE); + sendFakeMessage(nexxtv, iframe, 'pause'); + return p; + }) + .then(() => { + const p = listenOncePromise(nexxtv, VideoEvents.UNMUTED); + sendFakeMessage(nexxtv, iframe, 'unmute'); + return p; + }); + }); + }); + + + function sendFakeMessage(nexxtv, iframe, command) { + nexxtv.implementation_.handleNexxMessages_({ + origin: 'https://embed.nexx.cloud', + source: iframe.contentWindow, + data: JSON.stringify({ + cmd: command, + }), + }); + } +}); diff --git a/extensions/amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.html b/extensions/amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.html new file mode 100644 index 0000000000000..f1ab99cec664f --- /dev/null +++ b/extensions/amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.out b/extensions/amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.out new file mode 100644 index 0000000000000..68d1fccf79ac9 --- /dev/null +++ b/extensions/amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.out @@ -0,0 +1,3 @@ +FAIL +amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.html:59:4 The mandatory attribute 'data-mediaid' is missing in tag 'amp-nexxtv-player'. (see https://github.com/nexxtv/amphtml/blob/nexxtv-player/extensions/amp-nexxtv-player/amp-nexxtv-player.md) [AMP_TAG_PROBLEM] +amp-nexxtv-player/0.1/test/validator-amp-nexxtv-player.html:65:4 The mandatory attribute 'data-client' is missing in tag 'amp-nexxtv-player'. (see https://github.com/nexxtv/amphtml/blob/nexxtv-player/extensions/amp-nexxtv-player/amp-nexxtv-player.md) [AMP_TAG_PROBLEM] diff --git a/extensions/amp-nexxtv-player/0.1/validator-amp-nexxtv-player.protoascii b/extensions/amp-nexxtv-player/0.1/validator-amp-nexxtv-player.protoascii new file mode 100644 index 0000000000000..8361eaeaf5075 --- /dev/null +++ b/extensions/amp-nexxtv-player/0.1/validator-amp-nexxtv-player.protoascii @@ -0,0 +1,98 @@ +# +# Copyright 2017 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. +# + +tags: { # amp-nexxtv-player + html_format: AMP + tag_name: "SCRIPT" + spec_name: "amp-nexxtv-player extension .js script" + satisfies: "amp-nexxtv-player extension .js script" + mandatory_parent: "HEAD" + unique: true + extension_unused_unless_tag_present: "amp-nexxtv-player" + attrs: { + name: "async" + mandatory: true + value: "" + } + attrs: { + name: "custom-element" + mandatory: true + value: "amp-nexxtv-player" + dispatch_key: true + } + attrs: { name: "nonce" } + attrs: { + name: "src" + mandatory: true + value_regex: "https://cdn\\.ampproject\\.org/v0/amp-nexxtv-player-(latest|0\\.1)\\.js" + } + attrs: { + name: "type" + value_casei: "text/javascript" + } + cdata: { + blacklisted_cdata_regex: { + regex: "." + error_message: "contents" + } + } + spec_url: "https://github.com/nexxtv/amphtml/blob/nexxtv-player/extensions/amp-nexxtv-player/amp-nexxtv-player.md" +} +tags: { # + html_format: AMP + tag_name: "AMP-NEXXTV-PLAYER" + disallowed_ancestor: "AMP-SIDEBAR" + requires: "amp-nexxtv-player extension .js script" + attrs: { + name: "data-client" + mandatory: true + } + attrs: { + name: "data-mediaid" + mandatory: true + value_regex: "[^=/?:]+" + } + attrs: { + name: "data-mode" + value_regex: "api|static" + } + attrs: { + name: "data-origin" + value_url: { + allow_empty: true + allow_relative: true + allowed_protocol: "https" + allowed_protocol: "http" + } + } + attrs: { + name: "data-seek-to" + } + attrs: { + name: "data-streamtype" + value_regex: "album|audio|live|playlist|playlist-marked|video" + } + attr_lists: "extended-amp-global" + spec_url: "https://github.com/nexxtv/amphtml/blob/nexxtv-player/extensions/amp-nexxtv-player/amp-nexxtv-player.md" + amp_layout: { + supported_layouts: FILL + supported_layouts: FIXED + supported_layouts: FIXED_HEIGHT + supported_layouts: FLEX_ITEM + supported_layouts: NODISPLAY + supported_layouts: RESPONSIVE + } +} diff --git a/extensions/amp-nexxtv-player/amp-nexxtv-player.md b/extensions/amp-nexxtv-player/amp-nexxtv-player.md new file mode 100644 index 0000000000000..46667f5dcfb31 --- /dev/null +++ b/extensions/amp-nexxtv-player/amp-nexxtv-player.md @@ -0,0 +1,103 @@ + + +# `amp-nexxtv-player` + + + + + + + + + + + + + + + + + + +
DescriptionDisplays a media stream from nexxOMNIA platform
AvailabilityStable
Required Script<script async custom-element="amp-nexxtv-player" src="https://cdn.ampproject.org/v0/amp-nexxtv-player-0.1.js"></script>
Supported Layoutsfill, fixed, fixed-height, flex-item, nodisplay, responsive
+ +## Example + +With the responsive layout, the width and height from the example should yield correct layouts for 16:9 aspect ratio videos: + +```html + + +``` + +## Attributes + +**mediaid** + +* Required +* ID of your media you want to play + +**client** + +* Required +* your domain ID + +**streamtype** + +* optional +* default: video +* possible values: [video | audio | playlist | playlist-masked | live | album] +* playlist-masked: playlist without possibility to skip or choose video +* album: is an audio playlist + +**seek-to** + +Starting point of your media in seconds e.g. video starting 1:30min + +* optional + +**mode** + +* optional +* default: static +* possible values: [static | api] + + +**origin** + +Source from which embed domain media is played. + +* optional +* default: https://embed.nexx.cloud/ + + +**common attributes** + +This element includes [common attributes](https://www.ampproject.org/docs/reference/common_attributes) extended to AMP components. + +## Validation + +See [amp-nexxtv-player rules](https://github.com/ampproject/amphtml/blob/master/extensions/amp-nexxtv-player/0.1/validator-amp-nexxtv-player.protoascii) in the AMP validator specification. diff --git a/gulpfile.js b/gulpfile.js index fbafd38e78d0e..5fa2bb5f0fc08 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -92,6 +92,7 @@ declareExtension('amp-lightbox-viewer', '0.1', true, 'NO_TYPE_CHECK'); declareExtension('amp-list', '0.1', false, 'NO_TYPE_CHECK'); declareExtension('amp-live-list', '0.1', true); declareExtension('amp-mustache', '0.1', false, 'NO_TYPE_CHECK'); +declareExtension('amp-nexxtv-player', '0.1', false); declareExtension('amp-o2-player', '0.1', false, 'NO_TYPE_CHECK'); declareExtension('amp-ooyala-player', '0.1', false); declareExtension('amp-pinterest', '0.1', true, 'NO_TYPE_CHECK');