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

AMP jwplayer #2709

Merged
merged 1 commit into from
Mar 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions examples/jwplayer.amp.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!doctype html>
<html ⚡>
<head>
<meta charset="utf-8">
<title>JWPlayer AMP Example</title>
<link rel="canonical" href="amps.html" >
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<link href='https://fonts.googleapis.com/css?family=Georgia|Open+Sans|Roboto' rel='stylesheet' type='text/css'>
<script async custom-element="amp-jwplayer" src="https://cdn.ampproject.org/v0/amp-jwplayer-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>
<script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>
<h2>JWPlayer AMP Examples</h2>

<h3>Responsive</h3>

<amp-jwplayer
data-media-id="BZ6tc0gy"
data-player-id="uoIbMPm3"
layout="responsive" width="16" height="9">
</amp-jwplayer>

<h3>Non-responsive, with a playlist</h3>

<amp-jwplayer
data-playlist-id="482jsTAr"
data-player-id="uoIbMPm3"
width="480" height="270">
</amp-jwplayer>

</body>
</html>
125 changes: 125 additions & 0 deletions extensions/amp-jwplayer/0.1/amp-jwplayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright 2016 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 {getLengthNumeral, isLayoutSizeDefined} from '../../../src/layout';
import {loadPromise} from '../../../src/event-helper';
import {setStyles} from '../../../src/style';
import {user} from '../../../src/log';

class AmpJWPlayer extends AMP.BaseElement {

/** @override */
preconnectCallback(onLayout) {
// Host that serves player configuration and content redirects
this.preconnect.url('https://content.jwplatform.com', onLayout);
// CDN which hosts jwplayer assets
this.preconnect.url('https://ssl.p.jwpcdn.com', onLayout);
}

/** @override */
isLayoutSupported(layout) {
return isLayoutSizeDefined(layout);
}

/** @override */
buildCallback() {
const width = this.element.getAttribute('width');
const height = this.element.getAttribute('height');

/** @private @const {number} */
this.width_ = getLengthNumeral(width);

/** @private @const {number} */
this.height_ = getLengthNumeral(height);

/** @private @const {string} */
this.contentid_ = user.assert(
(this.element.getAttribute('data-playlist-id') ||
this.element.getAttribute('data-media-id')),
'Either the data-media-id or the data-playlist-id ' +
'attributes must be specified for <amp-jwplayer> %s',
this.element);

/** @private @const {string} */
this.playerid_ = user.assert(
this.element.getAttribute('data-player-id'),
'The data-player-id attribute is required for <amp-jwplayer> %s',
this.element);

if (!this.getPlaceholder()) {
this.buildImagePlaceholder_();
}
}


/** @override */
layoutCallback() {
const iframe = document.createElement('iframe');
const src = 'https://content.jwplatform.com/players/' +
encodeURIComponent(this.contentid_) + '-' +
encodeURIComponent(this.playerid_) + '.html';

iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allowfullscreen', 'true');
iframe.src = src;
this.applyFillContent(iframe);
iframe.width = this.width_;
iframe.height = this.height_;
this.element.appendChild(iframe);
/** @private {?Element} */
this.iframe_ = iframe;
return loadPromise(iframe);
}

/** @override */
pauseCallback() {
if (this.iframe_ && this.iframe_.contentWindow) {
// The /players page can respond to "play" and "pause" commands from the
// iframe's parent
this.iframe_.contentWindow./*OK*/postMessage('pause',
'https://content.jwplatform.com');
}
}

/** @private */
buildImagePlaceholder_() {
const imgPlaceholder = new Image();

setStyles(imgPlaceholder, {
'object-fit': 'cover',
});

imgPlaceholder.src = 'https://content.jwplatform.com/thumbs/' +
encodeURIComponent(this.contentid_) + '-720.jpg';
imgPlaceholder.setAttribute('placeholder', '');
imgPlaceholder.width = this.width_;
imgPlaceholder.height = this.height_;

this.applyFillContent(imgPlaceholder);

// Not every media item has a thumbnail image. If no image is found,
// don't add the placeholder to the DOM.
loadPromise(imgPlaceholder).then(() => {
this.element.appendChild(imgPlaceholder);
}).catch(() => {
// If the thumbnail image isn't available, we can safely ignore this
// error, and no image placeholder will be inserted.
});
}

};

AMP.registerElement('amp-jwplayer', AmpJWPlayer);
101 changes: 101 additions & 0 deletions extensions/amp-jwplayer/0.1/test/test-amp-jwplayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Copyright 2016 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} from '../../../../testing/iframe';
require('../amp-jwplayer');
import {adopt} from '../../../../src/runtime';

adopt(window);

describe('amp-jwplayer', () => {

function getjwplayer(attributes) {
return createIframePromise().then(iframe => {
const jw = iframe.doc.createElement('amp-jwplayer');
for (const key in attributes) {
jw.setAttribute(key, attributes[key]);
}
jw.setAttribute('width', '320');
jw.setAttribute('height', '180');
jw.setAttribute('layout', 'responsive');
iframe.doc.body.appendChild(jw);
jw.implementation_.layoutCallback();
return jw;
});
}

it('renders', () => {
return getjwplayer({
'data-media-id': 'Wferorsv',
'data-player-id': 'sDZEo0ea',
}).then(jw => {
const iframe = jw.querySelector('iframe');
expect(iframe).to.not.be.null;
expect(iframe.tagName).to.equal('IFRAME');
expect(iframe.src).to.equal(
'https://content.jwplatform.com/players/Wferorsv-sDZEo0ea.html');
expect(iframe.getAttribute('width')).to.equal('320');
expect(iframe.getAttribute('height')).to.equal('180');
});
});

it('renders with a playlist', () => {
return getjwplayer({
'data-playlist-id': '482jsTAr',
'data-player-id': 'sDZEo0ea',
}).then(jw => {
const iframe = jw.querySelector('iframe');
expect(iframe).to.not.be.null;
expect(iframe.tagName).to.equal('IFRAME');
expect(iframe.src).to.equal(
'https://content.jwplatform.com/players/482jsTAr-sDZEo0ea.html');
});
});

// These tests fail if the corresponding errors occur in buildCallback instead of
// layoutCallback. Commenting them out for now.
/*
it('fails if no media is specified', () => {
return getjwplayer({
'data-player-id': 'sDZEo0ea',
}).should.eventually.be.rejectedWith(
/Either the data-media-id or the data-playlist-id attributes must be/
);
});

it('fails if no player is specified', () => {
return getjwplayer({
'data-media-id': 'Wferorsv',
}).should.eventually.be.rejectedWith(
/The data-player-id attribute is required for/
);
});
*/

it('renders with a bad playlist', () => {
return getjwplayer({
'data-playlist-id': 'zzz',
'data-player-id': 'sDZEo0ea',
}).then(jw => {
const iframe = jw.querySelector('iframe');
expect(iframe).to.not.be.null;
expect(iframe.tagName).to.equal('IFRAME');
expect(iframe.src).to.equal(
'https://content.jwplatform.com/players/zzz-sDZEo0ea.html');
});
});

});
115 changes: 115 additions & 0 deletions extensions/amp-jwplayer/amp-jwplayer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<!---
Copyright 2016 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.
-->

### <a name="amp-jwplayer"></a> `amp-jwplayer`

<table>
<tr>
<td width="40%"><strong>Description</strong></td>
<td>An <code>amp-jwplayer</code> component displays a cloud-hosted JW Player.</td>
</tr>
<tr>
<td width="40%"><strong>Availability</strong></td>
<td>Stable</td>
</tr>
<tr>
<td width="40%"><strong>Required Script</strong></td>
<td><code>&lt;script async custom-element="amp-jwplayer" src="https://cdn.ampproject.org/v0/amp-jwplayer-0.1.js">&lt;/script></code></td>
</tr>
<tr>
<td width="40%"><strong>Examples</strong></td>
<td><a href="https://github.com/ampproject/amphtml/blob/master/examples/jwplayer.amp.html">jwplayer.amp.html</a></td>
</tr>
</table>

The following lists validation errors specific to the `amp-jwplayer` tag
(see also `amp-jwplayer` in the [AMP validator specification](https://github.com/ampproject/amphtml/blob/master/validator/validator.protoascii):

<table>
<tr>
<th width="40%"><strong>Validation Error</strong></th>
<th>Description</th>
</tr>
<tr>
<td width="40%"><a href="/docs/reference/validation_errors.html#tag-required-by-another-tag-is-missing">TAG_REQUIRED_BY_MISSING</a></td>
<td>Error thrown when required <code>amp-jwplayer</code> extension <code>.js</code> script tag is missing or incorrect.</td>
</tr>
<tr>
<td width="40%"><a href="/docs/reference/validation_errors.html#mandatory-attribute-missing">MANDATORY_ATTR_MISSING</a></td>
<td>Error thrown when <code>data-account</code> attribute is missing.</td>
</tr>
<tr>
<td width="40%"><a href="/docs/reference/validation_errors.html#implied-layout-isnt-supported-by-amp-tag">IMPLIED_LAYOUT_INVALID</a></td>
<td>Error thrown when implied layout is set to <code>CONTAINER</code>; this layout type isn't supported.</td>
</tr>
<tr>
<td width="40%"><a href="/docs/reference/validation_errors.html#mandatory-attribute-missing">MANDATORY_ONEOF_ATTR_MISSING</a></td>
<td>Error thrown when either the <code>data-media-id</code> or <code>data-playlist-id</code> attributes are missing.</td>
</tr>
<tr>
<td width="40%"><a href="/docs/reference/validation_errors.html#implied-layout-isnt-supported-by-amp-tag">IMPLIED_LAYOUT_INVALID</a></td>
<td>Error thrown when implied layout is set to <code>CONTAINER</code>; this layout type isn't supported.</td>
</tr>
<tr>
<td width="40%"><a href="/docs/reference/validation_errors.html#specified-layout-isnt-supported-by-amp-tag">SPECIFIED_LAYOUT_INVALID</a></td>
<td>Error thrown when specified layout is set to <code>CONTAINER</code>; this layout type isn't supported.</td>
</tr>
<tr>
<td width="40%"><a href="/docs/reference/validation_errors.html#invalid-property-value">INVALID_PROPERTY_VALUE_IN_ATTR_VALUE</a></td>
<td>Error thrown when invalid value is given for attributes <code>height</code> or <code>width</code>. For example, <code>height=auto</code> triggers this error for all supported layout types, with the exception of <code>NODISPLAY</code>.</td>
</tr>
</table>

#### Example

The `width` and `height` attributes determine the aspect ratio of the player embedded in responsive layouts.

Example:

```html
<amp-jwplayer
data-player-id="aBcD1234"
data-media-id="5678WxYz"
layout="responsive"
width="16" height="9">
</amp-jwplayer>
```

Non-responsive layout is also supported.

Example:

```html
<amp-jwplayer
data-player-id="aBcD1234"
data-playlist-id="5678WxYz"
width="160" height="90">
</amp-jwplayer>
```

#### Attributes

**data-player-id**

JW Platform player id. This is an 8-digit alphanumeric sequence that can be found in the [Players](https://dashboard.jwplayer.com/#/players) section in your JW Player Dashboard. (**Required**)

**data-media-id**

The JW Platform media id. This is an 8-digit alphanumeric sequence that can be found in the [Content](https://dashboard.jwplayer.com/#/content) section in your JW Player Dashboard. (**Required if `data-playlist-id` is not defined.**)

**data-playlist-id**

The JW Platform playlist id. This is an 8-digit alphanumeric sequence that can be found in the [Playlists](https://dashboard.jwplayer.com/#/content/playlists) section in your JW Player Dashboard. If both `data-playlist-id` and `data-media-id` are specified, `data-playlist-id` takes precedence. (**Required if `data-media-id` is not defined.**)
2 changes: 2 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ function buildExtensions(options) {
buildExtension('amp-iframe', '0.1', false, options);
buildExtension('amp-image-lightbox', '0.1', true, options);
buildExtension('amp-instagram', '0.1', false, options);
buildExtension('amp-jwplayer', '0.1', false, options);
buildExtension('amp-lightbox', '0.1', false, options);
buildExtension('amp-list', '0.1', false, options);
buildExtension('amp-mustache', '0.1', false, options);
Expand Down Expand Up @@ -340,6 +341,7 @@ function buildExamples(watch) {
buildExample('font.amp.html');
buildExample('facebook.amp.html');
buildExample('instagram.amp.html');
buildExample('jwplayer.amp.html');
buildExample('pinterest.amp.html');
buildExample('reach-player.amp.html');
buildExample('released.amp.html');
Expand Down