Skip to content
This repository has been archived by the owner on Nov 26, 2021. It is now read-only.

Commit

Permalink
Merge pull request #3 from Financial-Times/videojs
Browse files Browse the repository at this point in the history
Videojs
  • Loading branch information
Alberto Elias committed Jun 2, 2016
2 parents 4fe3478 + 311eba6 commit 2bb6445
Show file tree
Hide file tree
Showing 5 changed files with 431 additions and 0 deletions.
4 changes: 4 additions & 0 deletions main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,9 @@ $_o-video_applied: false !default;
}
}

[data-o-video-player="videojs"] .o-video--videojs {
position: absolute;
}

$_o-video_applied: true !global;
}
5 changes: 5 additions & 0 deletions src/models/video-factory.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
const Video = require('./video');
const Brightcove = require('./brightcove');
const VideoJsPlayer = require('./videojs');

module.exports = (el, opts) => {
const source = el.getAttribute('data-o-video-source').toLowerCase();
const player = (el.getAttribute('data-o-video-player') || 'html5').toLowerCase();
if (source === 'brightcove') {
if (player === 'videojs') {
return new VideoJsPlayer(el, opts);
}
return new Brightcove(el, opts);
} else {
return new Video(el, opts);
Expand Down
235 changes: 235 additions & 0 deletions src/models/videojs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/* global fetch, videojs */

const crossDomainFetch = require('o-fetch-jsonp').crossDomainFetch;
const Video = require('./video');
const getAppropriateRendition = require('../libs/get-appropriate-rendition');

let currentlyPlayingVideo = null;
let requestedVideo = null;
let videoJsPromise;
let videoJsPluginsPromise;
let videoElementIdOrder = 0;
let advertising;

const pauseOtherVideos = (video) => {
requestedVideo = video;
if(currentlyPlayingVideo && currentlyPlayingVideo !== requestedVideo){
currentlyPlayingVideo.pause();
}

currentlyPlayingVideo = video;
};

const ensureVideoJsLibraryLoaded = () => {
if (videoJsPromise) {
return videoJsPromise;
}

const videojsScript = document.createElement('script');
videojsScript.setAttribute('type', 'text/javascript');
videojsScript.setAttribute('src', `//vjs.zencdn.net/5.9.2/video.min.js`);
videojsScript.setAttribute('async', true);
videojsScript.setAttribute('defer', true);
document.getElementsByTagName("head")[0].appendChild(videojsScript);

const videojsStyles = document.createElement('link')
videojsStyles.setAttribute('rel', 'stylesheet')
videojsStyles.setAttribute('type', 'text/css')
videojsStyles.setAttribute('href', '//vjs.zencdn.net/5.9.2/video-js.min.css')
document.getElementsByTagName('head')[0].appendChild(videojsStyles);

videoJsPromise = new Promise(resolve => {
videojsScript.addEventListener('load', () => {
resolve();
});
});

return videoJsPromise;
}

const ensureVideoJsPluginsAreLoaded = () => {
if(videoJsPluginsPromise) {
return videoJsPluginsPromise;
}

if(advertising) {
const googleSdkScript = document.createElement('script');
googleSdkScript.setAttribute('type', 'text/javascript');
googleSdkScript.setAttribute('src', `//imasdk.googleapis.com/js/sdkloader/ima3.js`);
googleSdkScript.setAttribute('async', true);
googleSdkScript.setAttribute('defer', true);
document.getElementsByTagName("head")[0].appendChild(googleSdkScript);

let googleSdkScriptPromise = new Promise(sdkLoaded => {
googleSdkScript.addEventListener('load', () => {
sdkLoaded();
});
});

const videoJsAdPluginsScript = document.createElement('script');
videoJsAdPluginsScript.setAttribute('type', 'text/javascript');
videoJsAdPluginsScript.setAttribute('src', `https://next-geebee.ft.com/assets/videojs/videojs-plugins.min.js`);
videoJsAdPluginsScript.setAttribute('async', true);
videoJsAdPluginsScript.setAttribute('defer', true);
document.getElementsByTagName("head")[0].appendChild(videoJsAdPluginsScript);

let videoJsAdPluginsScriptPromise = new Promise(pluginsLoaded => {
videoJsAdPluginsScript.addEventListener('load', () => {
pluginsLoaded();
});
});

const videoJsAdPluginStyles = document.createElement('link')
videoJsAdPluginStyles.setAttribute('rel', 'stylesheet')
videoJsAdPluginStyles.setAttribute('type', 'text/css')
videoJsAdPluginStyles.setAttribute('href', 'https://next-geebee.ft.com/assets/videojs/videojs-plugins.min.css')
document.getElementsByTagName('head')[0].appendChild(videoJsAdPluginStyles);
videoJsPluginsPromise = Promise.all([googleSdkScriptPromise, videoJsAdPluginsScriptPromise]);
return videoJsPluginsPromise;
} else {
return Promise.resolve();
}
}

const ensureAllScriptsAreLoaded = () => {
return ensureVideoJsLibraryLoaded().then(() => {
return ensureVideoJsPluginsAreLoaded();
});
};


// use the image resizing service, if width supplied
const updatePosterUrl = (posterImage, width) => {
let url = `https://image.webservices.ft.com/v1/images/raw/${encodeURIComponent(posterImage)}?source=o-video`;
if (width) {
url += `&fit=scale-down&width=${width}`;
}
return url;
};

class VideoJsPlayer extends Video {
constructor(el, opts) {
advertising = opts && opts['advertising'] ? true : false;
ensureAllScriptsAreLoaded();
super(el, opts);
}

getData() {
const dataPromise = this.opts.data ? Promise.resolve(this.opts.data) : crossDomainFetch(`//next-video.ft.com/api/${this.id}`)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw Error('Brightcove responded with a ' + response.status + ' (' + response.statusText + ') for id ' + this.id);
}
});

return dataPromise.then(data => {
this.brightcoveData = data;
this.posterImage = updatePosterUrl(data.videoStillURL, this.opts.optimumWidth);
this.rendition = getAppropriateRendition(data.renditions);
});
}

renderVideo() {
if (this.rendition) {
if (this.opts.placeholder) {
this.addPlaceholder();
} else {
this.addVideo();
}
}
return this;
}

init() {
const initPromise = this.getData().then(() => this.renderVideo());
return Promise.all([initPromise, videoJsPromise]);
}

info() {
const date = new Date(+this.brightcoveData.publishedDate);
return {
posterImage: this.posterImage,
id: this.brightcoveData.id,
length: this.brightcoveData.length,
longDescription: this.brightcoveData.longDescription,
name: this.brightcoveData.name,
publishedDate: date.toISOString(),
publishedDateReadable: date.toUTCString(),
shortDescription: this.brightcoveData.shortDescription,
tags: this.brightcoveData.tags,
};
}

addVideo() {
let videoIdProperty = 'test-video-' + videoElementIdOrder++;
this.el = document.createElement('video');
this.el.setAttribute('poster', this.posterImage);
this.el.setAttribute('src', this.rendition.url);
this.el.setAttribute('id', videoIdProperty);
this.el.className = Array.isArray(this.classes) ? this.classes.join(' ') : this.classes;
this.el.classList.add('o-video--videojs');
this.containerEl.appendChild(this.el);
this.el.addEventListener('playing', () => pauseOtherVideos(this.el));
return ensureAllScriptsAreLoaded()
.then(() => {
let videoPlayer = videojs(videoIdProperty, {"controls": true,"autoplay": true,"preload": "auto"}).width('100%');
if(advertising) {
this.advertising(videoPlayer, videoIdProperty);
}
});
}

advertising(player, videoIdProperty) {
player.ima({
id: videoIdProperty,
adTagUrl: 'http://pubads.g.doubleclick.net/gampad/ads?env=vp&gdfp_req=1&impl=s&output=xml_vast2&iu=/5887/ft.com&sz=592x333|400x225&unviewed_position_start=1&scp=pos%3Dvideo'
});
player.ima.requestAds();
}

addPlaceholder() {
this.placeholderEl = document.createElement('img');
this.placeholderEl.setAttribute('src', this.posterImage);
this.placeholderEl.className = Array.isArray(this.classes) ? this.classes.join(' ') : this.classes;
this.containerEl.classList.add('o-video--placeholder');

this.containerEl.appendChild(this.placeholderEl);

let titleEl;
if (this.opts.placeholderTitle) {
titleEl = document.createElement('div');
titleEl.className = 'o-video__title';
titleEl.textContent = this.brightcoveData.name;
this.containerEl.appendChild(titleEl);
}

if (this.opts.playButton) {

const playButtonEl = document.createElement('button');
playButtonEl.className = 'o-video__play-button';
playButtonEl.textContent = 'Play video';

this.containerEl.appendChild(playButtonEl);

playButtonEl.addEventListener('click', () => {
this.containerEl.removeChild(playButtonEl);
if (titleEl) {
this.containerEl.removeChild(titleEl);
}
this.removePlaceholder();
this.addVideo();
this.el.focus();
});
}
}

removePlaceholder() {
this.containerEl.classList.remove('o-video--placeholder');
this.containerEl.removeChild(this.placeholderEl);
}

}

module.exports = VideoJsPlayer;
7 changes: 7 additions & 0 deletions test/models/video-factory.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
const videoFactory = require('../../src/models/video-factory');
const Brightcove = require('../../src/models/brightcove');
const Video = require('../../src/models/video');
const VideoJS = require('../../src/models/videojs');

describe('Video Factory', () => {

Expand All @@ -26,6 +27,12 @@ describe('Video Factory', () => {
videoFactory(containerEl).should.be.a.instanceof(Brightcove);
});

it('should create a VideoJS object if player is \'videojs\'', () => {
containerEl.setAttribute('data-o-video-source', 'brightcove');
containerEl.setAttribute('data-o-video-player', 'videojs');
videoFactory(containerEl).should.be.an.instanceOf(VideoJS);
});

it('should create a standard Video object if unknown source', () => {
containerEl.setAttribute('data-o-video-source', 'other');
videoFactory(containerEl).should.be.an.instanceOf(Video);
Expand Down
Loading

0 comments on commit 2bb6445

Please sign in to comment.