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 #2660

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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>
129 changes: 129 additions & 0 deletions extensions/amp-jwplayer/0.1/amp-jwplayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/**
* 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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does jwplayer support poster images that can be shown without loading the player?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cramforce We do support poster images in the platform, but not all media assets have them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out how we do this for youtube. It has handling for the case where the poster image is missing.

const height = this.element.getAttribute('height');

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

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

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


/** @override */
layoutCallback() {
/** @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);


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',
'visibility': 'hidden',
});

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

this.element.appendChild(imgPlaceholder);
this.applyFillContent(imgPlaceholder);

loadPromise(imgPlaceholder).then(() => {
setStyles(imgPlaceholder, {
'visibility': '',
});
}).catch(() => {
// Thumbnails aren't available for all media content.
// On a 404, remove the placeholder image.
this.element.removeChild(imgPlaceholder);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do this part as:

}).catch(() => {
  this.deferMutate(() => {
    this.element.removeChild(imgPlaceholder);
  });
});

Or alternatively, you can wait for image to load before adding it to DOM. That way you can avoid playing with visibility as well.

});
}

};

AMP.registerElement('amp-jwplayer', AmpJWPlayer);
97 changes: 97 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,97 @@
/**
* 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');
});
});

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**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please document which of these attributes are required?


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.

**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.

**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 a `data-playlist-id` and `data-media-id` are specified, `data-playlist-id` takes precedence.
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