diff --git a/demo/index.html b/demo/index.html
index 777611d9f..d351e3779 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -92,7 +92,7 @@
Player
>YouTube
- and
+ ,
+ and
+
@@ -237,6 +241,15 @@
Player
+
+
+ Ariana Grande - Stuck with U
+ on
+
+ DailyMotion
+
+
+
diff --git a/demo/src/js/sources.js b/demo/src/js/sources.js
index e21885af7..95b76d9e8 100644
--- a/demo/src/js/sources.js
+++ b/demo/src/js/sources.js
@@ -76,6 +76,15 @@ const sources = {
},
],
},
+ dailymotion: {
+ type: 'video',
+ sources: [
+ {
+ src: 'https://www.dailymotion.com/video/x7tritx',
+ provider: 'dailymotion',
+ },
+ ],
+ },
};
export default sources;
diff --git a/src/js/captions.js b/src/js/captions.js
index 98d7d6137..431b9507e 100644
--- a/src/js/captions.js
+++ b/src/js/captions.js
@@ -31,7 +31,7 @@ const captions = {
}
// Only Vimeo and HTML5 video supported at this point
- if (!this.isVideo || this.isYouTube || (this.isHTML5 && !support.textTracks)) {
+ if (!this.isVideo || this.isYouTube || this.isDailyMotion || (this.isHTML5 && !support.textTracks)) {
// Clear menu and hide
if (
is.array(this.config.controls) &&
diff --git a/src/js/config/defaults.js b/src/js/config/defaults.js
index 7a73c318a..1e18c2216 100644
--- a/src/js/config/defaults.js
+++ b/src/js/config/defaults.js
@@ -206,6 +206,11 @@ const defaults = {
sdk: 'https://www.youtube.com/iframe_api',
api: 'https://noembed.com/embed?url=https://www.youtube.com/watch?v={0}',
},
+ dailymotion: {
+ sdk: 'https://api.dmcdn.net/all.js',
+ api:
+ 'https://api.dailymotion.com/video/{0}?fields=aspect_ratio,duration,embed_url,height,thumbnail_1080_url,width,title',
+ },
googleIMA: {
sdk: 'https://imasdk.googleapis.com/js/sdkloader/ima3.js',
},
@@ -440,6 +445,16 @@ const defaults = {
customControls: true,
noCookie: false, // Whether to use an alternative version of YouTube without cookies
},
+
+ // DailyMotion plugin
+ dailymotion: {
+ api: 'postMessage', // Enable the Player API https://developer.dailymotion.com/player/#player-parameters
+ 'autoplay-mute': false, // Try to autoplay while muting the video if autoplay didn't work
+ syndication: '', // Syndication key for the player
+ 'ui-logo': false, // Hide the dailymotion logo
+ 'ui-start-screen-info': false, // Hide video information (title and owner) on the start screen
+ apimode: 'queryString', // How to encode/decode messages sent from the player. https://developer.dailymotion.com/player/#player-parameters
+ },
};
export default defaults;
diff --git a/src/js/config/types.js b/src/js/config/types.js
index 31e488eb8..1a760f02d 100644
--- a/src/js/config/types.js
+++ b/src/js/config/types.js
@@ -6,6 +6,7 @@ export const providers = {
html5: 'html5',
youtube: 'youtube',
vimeo: 'vimeo',
+ dailymotion: 'dailymotion',
};
export const types = {
@@ -28,6 +29,11 @@ export function getProviderByUrl(url) {
return providers.vimeo;
}
+ // DailyMotion
+ if (/^(https?:\/\/)?(www\.)?(dailymotion\.com|dai\.ly)\/.+$/.test(url)) {
+ return providers.dailymotion;
+ }
+
return null;
}
diff --git a/src/js/media.js b/src/js/media.js
index ddac5ebf3..5f929b733 100644
--- a/src/js/media.js
+++ b/src/js/media.js
@@ -3,6 +3,7 @@
// ==========================================================================
import html5 from './html5';
+import dailymotion from './plugins/dailymotion';
import vimeo from './plugins/vimeo';
import youtube from './plugins/youtube';
import { createElement, toggleClass, wrap } from './utils/elements';
@@ -53,6 +54,8 @@ const media = {
youtube.setup.call(this);
} else if (this.isVimeo) {
vimeo.setup.call(this);
+ } else if (this.isDailyMotion) {
+ dailymotion.setup.call(this);
}
},
};
diff --git a/src/js/plugins/dailymotion.js b/src/js/plugins/dailymotion.js
new file mode 100644
index 000000000..e92b487e9
--- /dev/null
+++ b/src/js/plugins/dailymotion.js
@@ -0,0 +1,298 @@
+// ==========================================================================
+// Dailymotion plugin
+// ==========================================================================
+
+import ui from '../ui';
+import { createElement, replaceElement, toggleClass } from '../utils/elements';
+import { triggerEvent } from '../utils/events';
+import fetch from '../utils/fetch';
+import is from '../utils/is';
+import loadScript from '../utils/load-script';
+import { extend } from '../utils/objects';
+import { format } from '../utils/strings';
+import { setAspectRatio } from '../utils/style';
+
+// Parse DailyMotion ID from URL
+function parseId(url) {
+ if (is.empty(url)) {
+ return null;
+ }
+
+ if (is.number(Number(url))) {
+ return url;
+ }
+
+ const regex = /^.*(dai.ly\/|dailymotion.com\/|video\/)(\w+).*/;
+ return url.match(regex) ? RegExp.$2 : url;
+}
+
+// Set playback state and trigger change (only on actual change)
+function assurePlaybackState(play) {
+ if (play && !this.embed.hasPlayed) {
+ this.embed.hasPlayed = true;
+ }
+ if (this.media.paused === play) {
+ this.media.paused = !play;
+ triggerEvent.call(this, this.media, play ? 'play' : 'pause');
+ }
+}
+
+const dailymotion = {
+ setup() {
+ const player = this;
+ // Add embed class for responsive
+ toggleClass(player.elements.wrapper, player.config.classNames.embed, true);
+
+ // Can't set speed for dailymotion
+ player.options.speed = [1];
+
+ // Set intial ratio
+ setAspectRatio.call(player);
+
+ // Load the SDK if not already
+ if (is.object(window.DM) && is.function(window.DM.player)) {
+ dailymotion.ready.call(player);
+ } else {
+ loadScript(player.config.urls.dailymotion.sdk)
+ .then(() => {
+ dailymotion.ready.call(player);
+ })
+ .catch(error => {
+ player.debug.warn('DailyMotion SDK (all.js) failed to load', error);
+ });
+ }
+ },
+
+ // API Ready
+ ready() {
+ // Following previous plugin procedure
+ const player = this;
+ // Getting dailymotion config
+ const config = player.config.dailymotion;
+ // Get the source URL or ID if it's an
+ let source = player.media.getAttribute('src');
+
+ // Get from if needed
+ if (is.empty(source)) {
+ source = player.media.getAttribute(this.config.attributes.embed.id);
+ }
+ const videoId = parseId(source);
+ // Get poster, if already set
+ const { poster } = player;
+ // This div will be replaced by an iframe by DM.player
+ const container = createElement('div');
+ // We need to keep the previous element to implement the destroy method and keep the same behaviour as Youtube and Vimeo
+ const previousElement = player.media.cloneNode();
+ player.media = replaceElement(container, player.media);
+
+ // Get poster image
+ fetch(format(player.config.urls.dailymotion.api, videoId), 'json')
+ .then(response => {
+ if (is.empty(response)) {
+ return;
+ }
+ // Set Title
+ player.config.title = response.title;
+ ui.setTitle.call(player);
+
+ // Set aspect ratio
+ player.embed.ratio = [response.width, response.height];
+
+ // Set and show poster
+ ui.setPoster.call(player, response.thumbnail_1080_url).catch(() => {});
+ setAspectRatio.call(player);
+ })
+ .catch(() => {
+ setAspectRatio(player);
+ });
+
+ // Setup instance
+ // https://developer.dailymotion.com/tools/sdks#sdk-javascript-player-api
+ player.embed = window.DM.player(container, {
+ video: videoId,
+ params: extend(
+ {},
+ {
+ autoplay: player.config.autoplay, // Autoplay
+ controls: !player.supported.ui,
+ mute: player.config.muted,
+ origin: window.location.hostname,
+ 'subtitles-default': player.config.captions.language,
+ },
+ config,
+ ),
+ });
+ container.setAttribute('data-poster', poster);
+ player.embed.destroy = () => {
+ player.media = replaceElement(previousElement, player.media);
+ };
+ player.media.paused = true;
+
+ player.embed.addEventListener('apiready', () => {
+ // Set the tabindex to avoid focus entering iframe
+ if (player.supported.ui) {
+ player.media.setAttribute('tabindex', -1);
+ }
+
+ player.media.currentTime = 0;
+ player.media.duration = player.embed.duration;
+ triggerEvent.call(player, player.media, 'timeupdate');
+ triggerEvent.call(player, player.media, 'durationchange');
+
+ // Create a faux HTML5 API using the DailyMotion API
+ /*
+ * Methods
+ */
+ player.media.play = () => {
+ assurePlaybackState.call(player, true);
+ return player.embed.play();
+ };
+
+ player.media.pause = () => {
+ assurePlaybackState.call(player, false);
+ return player.embed.pause();
+ };
+
+ player.media.stop = () => {
+ player.embed.pause();
+ player.embed.seek(0);
+ };
+
+ /*
+ * Properties
+ */
+ let { currentTime } = player.embed;
+ // Seeking
+ Object.defineProperty(player.media, 'currentTime', {
+ get() {
+ return currentTime;
+ },
+ set(time) {
+ // If paused and never played, mute audio preventively
+ if (player.paused && !player.embed.hasPlayed) {
+ player.embed.setMuted(true);
+ }
+
+ // Set seeking state and trigger event
+ player.media.seeking = true;
+ triggerEvent.call(player, player.media, 'seeking');
+
+ // Seek after events sent
+ player.embed.seek(time);
+ if (player.paused && !player.embed.hasPlayed) {
+ player.pause();
+ player.embed.setMuted(false);
+ }
+ },
+ });
+
+ // Volume
+ let { volume } = player.config;
+ Object.defineProperty(player.media, 'volume', {
+ get() {
+ return volume;
+ },
+ set(input) {
+ player.embed.setVolume(input);
+ volume = input;
+ triggerEvent.call(player, player.media, 'volumechange');
+ },
+ });
+ // Muted
+ let { muted } = player.config;
+ Object.defineProperty(player.media, 'muted', {
+ get() {
+ return muted;
+ },
+ set(input) {
+ const toggle = is.boolean(input) ? input : false;
+ muted = toggle;
+ player.embed.setMuted(toggle);
+ triggerEvent.call(player, player.media, 'volumechange');
+ },
+ });
+
+ // DailyMotion doesn't provide the source
+ Object.defineProperty(player.media, 'currentSrc', {
+ get() {
+ return '';
+ },
+ });
+
+ // Ended
+ Object.defineProperty(player.media, 'ended', {
+ get() {
+ return player.embed.ended;
+ },
+ });
+
+ /*
+ * Events Listeners
+ */
+ player.embed.addEventListener('error', () => {
+ // DailyMotion may fire onError twice, so only handle it once
+ if (!player.media.error) {
+ const { code, message } = player.embed.error;
+ player.media.error = { code, message };
+
+ triggerEvent.call(player, player.media, 'error');
+ }
+ });
+
+ player.embed.addEventListener('play', () => {
+ assurePlaybackState.call(player, true);
+ triggerEvent.call(player, player.media, 'playing');
+ });
+
+ player.embed.addEventListener('pause', () => {
+ assurePlaybackState.call(player, false);
+ });
+
+ player.embed.addEventListener('durationchange', () => {
+ player.media.duration = player.embed.duration;
+ triggerEvent.call(player, player.media, 'durationchange');
+ });
+
+ player.embed.addEventListener('timeupdate', () => {
+ player.media.seeking = false;
+ currentTime = player.embed.currentTime;
+ triggerEvent.call(player, player.media, 'timeupdate');
+ });
+
+ player.embed.addEventListener('progress', () => {
+ const buffered = player.embed.bufferedTime / player.embed.duration; // percent [0; 1]
+ player.media.buffered = buffered;
+ triggerEvent.call(player, player.media, 'progress');
+
+ // Check all loaded
+ if (parseInt(buffered, 10) === 1) {
+ triggerEvent.call(player, player.media, 'canplaythrough');
+ }
+ });
+ player.embed.addEventListener('seeked', () => {
+ player.media.seeking = false;
+ triggerEvent.call(player, player.media, 'seeked');
+ });
+
+ player.embed.addEventListener('ended', () => {
+ assurePlaybackState.call(player, false);
+ // DailyMotion doesn't support loop for a single video, so mimick it.
+ if (player.media.loop) {
+ // DailyMotion needs a call to `stop` before playing again
+ player.media.stop();
+ player.media.play();
+ } else {
+ triggerEvent.call(player, player.media, 'ended');
+ }
+ });
+ player.embed.addEventListener('waiting', () => {
+ triggerEvent.call(player, player.media, 'waiting');
+ });
+
+ // Rebuild UI
+ setTimeout(() => ui.build.call(player), 50);
+ });
+ },
+};
+
+export default dailymotion;
diff --git a/src/js/plyr.d.ts b/src/js/plyr.d.ts
index 479cfa98d..244833bf8 100644
--- a/src/js/plyr.d.ts
+++ b/src/js/plyr.d.ts
@@ -149,7 +149,7 @@ declare class Plyr {
readonly provider: Plyr.Provider;
/**
- * Returns the native API for Vimeo or Youtube players
+ * Returns the native API for Vimeo, Youtube or DailyMotion players
*/
readonly embed?: any;
@@ -244,7 +244,7 @@ declare class Plyr {
declare namespace Plyr {
type MediaType = 'audio' | 'video';
- type Provider = 'html5' | 'youtube' | 'vimeo';
+ type Provider = 'html5' | 'vimeo' | 'youtube' | 'dailymotion';
type StandardEventMap = {
progress: PlyrEvent;
playing: PlyrEvent;
@@ -507,6 +507,11 @@ declare namespace Plyr {
*/
youtube?: object;
+ /**
+ * DailyMotion Player Options.
+ */
+ dailymotion?: object;
+
/**
* Preview Thumbnails Options.
*/
diff --git a/src/js/plyr.js b/src/js/plyr.js
index b40f5c5a5..5675d521d 100644
--- a/src/js/plyr.js
+++ b/src/js/plyr.js
@@ -152,7 +152,7 @@ class Plyr {
this.elements.original = clone;
// Set media type based on tag or data attribute
- // Supported: video, audio, vimeo, youtube
+ // Supported: video, audio, vimeo, youtube, dailymotion
const type = this.media.tagName.toLowerCase();
// Embed properties
let iframe = null;
@@ -333,7 +333,7 @@ class Plyr {
}
get isEmbed() {
- return this.isYouTube || this.isVimeo;
+ return this.isYouTube || this.isVimeo || this.isDailyMotion;
}
get isYouTube() {
@@ -344,6 +344,10 @@ class Plyr {
return this.provider === providers.vimeo;
}
+ get isDailyMotion() {
+ return this.provider === providers.dailymotion;
+ }
+
get isVideo() {
return this.type === types.video;
}
@@ -491,7 +495,7 @@ class Plyr {
get buffered() {
const { buffered } = this.media;
- // YouTube / Vimeo return a float between 0-1
+ // YouTube / Vimeo / DailyMotion return a float between 0-1
if (is.number(buffered)) {
return buffered;
}
@@ -700,6 +704,11 @@ class Plyr {
return 0.5;
}
+ if (this.isDailyMotion) {
+ // No playback rate support
+ return 1;
+ }
+
// https://stackoverflow.com/a/32320020/1191319
return 0.0625;
}
@@ -718,6 +727,11 @@ class Plyr {
return 2;
}
+ if (this.isDailyMotion) {
+ // No Playback rate support
+ return 1;
+ }
+
// https://stackoverflow.com/a/32320020/1191319
return 16;
}
@@ -1207,6 +1221,14 @@ class Plyr {
// Vimeo does not always return
setTimeout(done, 200);
+ } else if (this.isDailyMotion) {
+ // Destroy DailyMotion made function
+ if (this.embed !== null && is.function(this.embed.destroy)) {
+ this.embed.destroy();
+ }
+
+ // Clean up
+ done();
}
};
@@ -1219,7 +1241,7 @@ class Plyr {
/**
* Check for support
* @param {String} type - Player type (audio/video)
- * @param {String} provider - Provider (html5/youtube/vimeo)
+ * @param {String} provider - Provider (html5/youtube/vimeo/dailymotion)
* @param {Boolean} inline - Where player has `playsinline` sttribute
*/
static supported(type, provider, inline) {
diff --git a/src/js/utils/i18n.js b/src/js/utils/i18n.js
index 708685272..1cc179d9c 100644
--- a/src/js/utils/i18n.js
+++ b/src/js/utils/i18n.js
@@ -13,6 +13,7 @@ const resources = {
html5: 'HTML5',
vimeo: 'Vimeo',
youtube: 'YouTube',
+ dailymotion: 'DailyMotion',
};
const i18n = {