├── README.md
├── embetter-builder.js
├── embetter.css
├── embetter.js
├── favicon.ico
├── index.html
├── playlist
└── index.html
├── sample-data
├── bandcamp-album.html
├── bandcamp-track.html
├── imgur-gallery.html
├── imgur-gifv.html
├── imgur.html
├── mixcloud.json
├── slideshare.json
├── soundcloud-set-oembed.json
├── soundcloud-set.json
├── soundcloud-track-oembed.json
├── soundcloud-track.json
├── ustream-recorded.json
├── ustream.json
├── vimeo.json
└── vine.json
└── vendor
├── normalize.css
├── reqwest.min.js
└── skeleton.css
/README.md:
--------------------------------------------------------------------------------
1 | # Embetter
2 |
3 | #### Because iframes are janky
4 |
5 | Media embeds can quickly bog your site down, so let's lazy-load them! The basic Embetter player consists of a tiny template with a progressively-enhanced thumbnail image, a play button, and the essential data needed to construct the responsive iframe embed code. Add a dash of javascript & css to your web page, and you have a simple, lightweight media player.
6 |
7 | #### Mobile-happy
8 |
9 | Since media generally can't autoplay on mobile devices, we work around that by populating the embed's iframe when an Embetter player is at least partially visible in the mobile viewport. By doing this, we still lazy-load (and unload) as much as possible, but each embed only needs a single tap to start playing.
10 |
11 | #### Demo
12 |
13 | Check out the [demo](http://cacheflowe.github.io/embetter) with the embed builder, see an [auto-playthrough playlist](http://cacheflowe.github.io/embetter/playlist/#/2-step-chunes/oG_La5R9HQk|C4xDtB_9SZM|QFxdkHRpz7Q|0Yt_Ts26PLM|VIOMoOYOTQQ|IlNNrXzi60o|F73WxhZPvJ4|4_uggscguzs|5sDW7AMoHVs|gXCN1DhHTZA|xy3RjUX3UeQ|ewHYBOCypXc|uNWTlETtuGM|B1JK9FJf0cE|mIE92NdE4ww|qpa09-OuZek|IqrYbXNnGmc|yUrOSCkoJ6w|VDqBbSBjsuw|nves7T9ThZI|OhKypM3H0iY|Z7nvb8kTPl8|Shtc5vtjui0|Aw4I7-jHw7s|YDi9i5JT5Co|lZ3KM5E8wl8|E48rwBeHf-A|MFu-PSawX1k|SgaHZIobQms|oG_La5R9HQk|mapbdUAbcrY) or see it out [in the wild](http://plasticsoundsupply.com/video).
14 |
15 | #### Usage
16 |
17 | Add Embetter embed codes to your markup:
18 |
19 | ```
20 |
21 |
22 |
23 | ```
24 |
25 | Tell Embetter's JavaScript to activate any players on the page, or within a specific container. Be sure to pass in the 3rd-party services you'd like to enable:
26 |
27 | ```
28 | var embedServices = [
29 | window.embetter.services.youtube,
30 | window.embetter.services.vimeo,
31 | window.embetter.services.soundcloud
32 | ];
33 | window.embetter.utils.initMediaPlayers(document.body, embedServices);
34 | ```
35 |
36 | Dispose any existing players before you switch pages in your single-page app:
37 |
38 | ```
39 | window.embetter.utils.disposePlayers();
40 | ```
41 |
42 | Stop all active embeds, in case you need to:
43 |
44 | ```
45 | window.embetter.utils.unembedPlayers(document.body)
46 | ```
47 |
48 | Get media complete callbacks from YouTube, Vimeo and Soundcloud embeds via their iframe .js APIs. This is useful to scroll your page to the next player contained in an element with `data-embetter-playlist="true"`.
49 |
50 | ```
51 | window.embetter.apiEnabled = true;
52 | window.embetter.apiAutoplayCallback = function(playerEl) {
53 | window.scrollTo(0, window.scrollY - playerEl.offsetTop + 50);
54 | };
55 |
56 | ```
57 |
58 | #### How it works
59 |
60 | On it's own, an Embetter embed code is just a clickable thumbnail that takes you to the source 3rd-party media page. After activation via `initMediaPlayers`, each Embetter player becomes clickable and has all of the data it needs to construct an iframe, which stretches to match the size of the preview thumbnail. This information is stored as a data attribute on the `.embetter` wrapper div and is extracted via API calls, regex capturing, or metatag scraping. Additional operations on this data helps us handle special cases per service. The `embetter-builder.js` file contains the logic to properly extract the necessary data for each type of embed, and operates on URLs, CORS-enabled APIs (usually oembed services), or locally-`curl`ed demo files to explain and test the behavior. Most likely, you'd want to port this behavior to your backend if you need to do this on the fly. Some services have several types of embeds that require different data to be stored, and some have extra css that adds custom layout and state behavior. When it comes down to it, however, we only need a single string (an id of some kind) to interpolate into a predefined iframe src template per service.
61 |
62 |
63 | ## Supported services & URL formats:
64 |
65 | ##### YouTube
66 | * Formats:
67 | * `https://www.youtube.com/watch?v=Fb4bCgWkZRc`
68 | * `https://youtube.com/watch?v=Fb4bCgWkZRc`
69 | * `https://youtube.com/v/Fb4bCgWkZRc`
70 | * `http://youtu.be/Fb4bCgWkZRc`
71 | * Regex:
72 | * `/(?:.+?)?(?:youtube\.com\/v\/|watch\/|\?v=|\&v=|youtu\.be\/|\/v=|^youtu\.be\/)([a-zA-Z0-9_-]{11})+/`
73 | * Captures id: `Fb4bCgWkZRc`
74 | * API URL:
75 | * None needed
76 | * Thumbnail aspect ratio:
77 | * 4:3
78 |
79 | ##### Vimeo
80 | * Formats:
81 | * `https://vimeo.com/99276873`
82 | * Regex:
83 | * `/(?:https?:\/\/)?(?:w{3}\.)?vimeo.com\/(\S*)(?:\/?|$|\s|\?|#)/`
84 | * Captures id: `99276873`
85 | * API URL (CORS/jsonp enabled):
86 | * `https://vimeo.com/api/v2/video/` + videoId + `.json`
87 | * Thumbnail aspect ratio:
88 | * Variable (matches uploaded media)
89 |
90 | ##### Soundcloud
91 | * Formats:
92 | * `https://soundcloud.com/itemsandthings/chlo-live-items-things`
93 | * `https://snd.sc/cacheflowe/sets/automate-everything-2005`
94 | * `https://soundcloud.com/groups/berlin-minimal-techno`
95 | * Regex:
96 | * `/(?:https?:\/\/)?(?:w{3}\.)?(?:soundcloud.com|snd.sc)\/([a-zA-Z0-9_-]*(?:\/sets)?(?:\/groups)?\/[a-zA-Z0-9_-]*)(?:\/?|$|\s|\?|#)/`
97 | * Captures path: `itemsandthings/chlo-live-items-things` or `cacheflowe/sets/automate-everything-2005`
98 | * API URL (CORS/jsonp enabled):
99 | * `http://api.soundcloud.com/resolve.json?url=` + mediaUrl + `&client_id=` + YourClientID + `&callback=jsonpResponse`
100 | * Thumbnail aspect ratio:
101 | * 1:1
102 |
103 | ##### Instagram
104 | * Formats:
105 | * `https://instagram.com/p/xekoQiQY3-/`
106 | * `http://instagr.am/p/xekoQiQY3-/`
107 | * Regex:
108 | * `/(?:https?:\/\/)?(?:w{3}\.)?(?:instagram.com|instagr.am)\/p\/([a-zA-Z0-9-_]*)(?:\/?|$|\s|\?|#)/`
109 | * Captures id: `xekoQiQY3-`
110 | * API URL:
111 | * None needed
112 | * Thumbnail aspect ratio:
113 | * 1:1
114 |
115 | ##### Giphy
116 | * Formats:
117 | * `https://giphy.com/gifs/ken-lee-3ESp1RAn7PjOw`
118 | * `https://giphy.com/gifs/3ESp1RAn7PjOw`
119 | * Regex:
120 | * `/(?:https?:\/\/)?(?:w{3}\.)?giphy.com\/gifs\/([a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
121 | * Captures id: `3ESp1RAn7PjOw`
122 | * API URL:
123 | * None needed
124 | * Thumbnail aspect ratio:
125 | * Variable (matches original .gif)
126 |
127 | ##### Mixcloud
128 | * Formats:
129 | * `https://www.mixcloud.com/Davealex/davealex-30m-electro-2010/`
130 | * Regex:
131 | * `/(?:https?:\/\/)?(?:w{3}\.)?(?:mixcloud.com)\/(.*\/.*)(?:\/?|$|\s|\?|#)/`
132 | * Captures id: `Davealex/davealex-30m-electro-2010`
133 | * API URL (CORS/jsonp enabled):
134 | * `http://www.mixcloud.com/oembed/?url=` + mediaUrl + `&format=jsonp`
135 | * Thumbnail aspect ratio:
136 | * 1:1
137 |
138 | ##### Dailymotion
139 | * Formats:
140 | * `http://www.dailymotion.com/video/x2681lh_the-ultimate-fainting-fails-compilation_fun`
141 | * `http://www.dailymotion.com/video/x2681lh`
142 | * Regex:
143 | * `/(?:https?:\/\/)?(?:w{3}\.)?dailymotion.com\/video\/([a-zA-Z0-9-_]*)(?:\/?|$|\s|\?|#)/`
144 | * Captures id: `x2681lh`
145 | * API URL:
146 | * None needed
147 | * Thumbnail aspect ratio:
148 | * 4:3
149 |
150 | ##### CodePen
151 | * Formats:
152 | * `http://codepen.io/nicoptere/pen/mgpxB`
153 | * `http://codepen.io/nicoptere/embed/mgpxB`
154 | * Regex:
155 | * `/(?:https?:\/\/)?(?:w{3}\.)?(?:codepen.io)\/([a-zA…A-Z0-9_\-%]*\/[a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
156 | * Captures id: `nicoptere/embed/mgpxB`
157 | * API URL:
158 | * None needed
159 | * Thumbnail aspect ratio:
160 | * 1024:600
161 |
162 | ##### Shadertoy
163 | * Formats:
164 | * `https://www.shadertoy.com/view/4dfGzs`
165 | * Regex:
166 | * `/(?:https?:\/\/)?(?:w{3}\.)?shadertoy.com\/view\/([a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
167 | * Captures id: `4dfGzs`
168 | * API URL:
169 | * None needed
170 | * Thumbnail aspect ratio:
171 | * 16:9
172 |
173 | ##### Bandcamp
174 | * Formats:
175 | * `https://swindleuk.bandcamp.com/album/swindle-walters-call`
176 | * `https://swindleuk.bandcamp.com/track/summer-fruits`
177 | * Regex:
178 | * `/(?:https?:\/\/)?(?:w{3}\.)?([a-zA-Z0-9_\-]*.bandcamp.com\/(album|track)\/[a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
179 | * Captures id: `album=2659930103` and `track=1312622119`
180 | * API URL:
181 | * Must scrape metatags from the album/track page.
182 | * Thumbnail aspect ratio:
183 | * 1:1
184 |
185 | ##### Ustream
186 | * Formats:
187 | * `http://www.ustream.tv/channel/almost-home-adoptions-cat-cam`
188 | * `http://www.ustream.tv/recorded/69957739`
189 | * `http://www.ustream.tv/NASAHDTV`
190 | * Regex:
191 | * `/(?:https?:\/\/)?(?:w{3}\.)?(?:ustream.tv|ustre.am)\/((?:(recorded|channel)\/)?[a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
192 | * Captures id: `channel/almost-home-adoptions-cat-cam` or `recorded/69957739` or `NASAHDTV`
193 | * API URL:
194 | * `http://www.ustream.tv/oembed?url=` + mediaUrl
195 | * Thumbnail aspect ratio:
196 | * 4:3 most of the time...
197 | * 10:9 or possibly otherwise
198 |
199 | ##### Imgur
200 | * Formats:
201 | * `http://imgur.com/gallery/iKQET`
202 | * `http://imgur.com/USbuZSo`
203 | * Regex:
204 | * `/(?:https?:\/\/)?(?:w{3}\.)?(?:imgur.com)\/((?:gallery\/)?[a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
205 | * Captures id: `gallery/iKQET` and `USbuZSo`
206 | * API URL:
207 | * `http://api.imgur.com/oembed.json?url=` + mediaUrl
208 | * Must scrape metatags from the Imgur page, since the oembed service doesn't provide a thumbnail URL.
209 | * Thumbnail aspect ratio:
210 | * Variable (matches uploaded media)
211 |
212 | ##### Vine
213 | * Formats:
214 | * `https://vine.co/v/eWlADOIAEAd`
215 | * Regex:
216 | * `/(?:https?:\/\/)?(?:w{3}\.)?vine.co\/v\/([a-zA-Z0-9-]*)(?:\/?|$|\s|\?|#)/`
217 | * Captures id: `eWlADOIAEAd`
218 | * API URL:
219 | * `https://vine.co/oembed/` + vineId + `.json`
220 | * Thumbnail aspect ratio:
221 | * 1:1
222 |
223 | ##### Slideshare
224 | * Formats:
225 | * `http://www.slideshare.net/HunterLoftis1/forwardjs-we-will-all-be-game-developers`
226 | * Regex:
227 | * `/(?:https?:\/\/)?(?:w{3}\.)?slideshare.net\/([a-zA-Z0-9_\-%]*\/[a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
228 | * Captures id: `eWlADOIAEAd`
229 | * API URL:
230 | * `http://www.slideshare.net/api/oembed/2?url=https://www.slideshare.net/` + slideshowId + `&format=json'`
231 | * Thumbnail aspect ratio:
232 | * 170:96
233 | * 170:121
234 | * 170:128
235 | * Variable, but 170px width
236 |
237 | ##### Kuula
238 | * Formats:
239 | * `https://kuula.co/post/7fWCb`
240 | * Regex:
241 | * `/(?:https?:\/\/)?(?:w{3}\.)?kuula.co\/post\/([a-zA-Z0-9_\-%]*)(?:\/?|$|\s|\?|#)/`
242 | * Captures id: `7fWCb`
243 | * API URL:
244 | * None needed
245 | * Thumbnail aspect ratio:
246 | * 1:1
247 |
248 |
249 |
250 | ### TODO:
251 |
252 | * More documentation about how each service gathers IDs to create iframes - add iframe src construction example
253 | * Usage documentation - full API
254 | * Add info about special per-service handling
255 | * Make Slideshare iframe responsive to thumbnail aspect ratio
256 |
--------------------------------------------------------------------------------
/embetter-builder.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | var embetter = window.embetter;
3 |
4 | /////////////////////////////////////////////////////////////
5 | // BUILD PLAYER FROM PASTE
6 | /////////////////////////////////////////////////////////////
7 | embetter.utils.buildPlayerFromServiceURL = function(el, string, services) {
8 | for (var i = 0; i < services.length; i++) {
9 | var service = services[i];
10 | if(string.match(service.regex) != null) {
11 | service.buildFromText(string, el);
12 | }
13 | }
14 | };
15 |
16 | embetter.utils.playerCode = function(htmlStr) {
17 | var entityMap = {
18 | "<": "<",
19 | ">": ">",
20 | };
21 | function escapeHtml(string) {
22 | return String(string).replace(/[<>]/g, function (s) {
23 | return entityMap[s];
24 | });
25 | }
26 | htmlStr = htmlStr.replace(/\>\s+\<'); // remove whitespace between tags
27 | return 'Embed code:
';
28 | };
29 |
30 | embetter.utils.embedPlayerInContainer = function(containerEl, serviceObj, mediaUrl, thumbnail, id) {
31 | // create service title
32 | containerEl.appendChild(embetter.utils.stringToDomElement('' + serviceObj.type.toUpperCase() + ' '));
33 | // create embed
34 | var newEmbedHTML = embetter.utils.playerHTML(serviceObj, mediaUrl, thumbnail, id);
35 | var newEmbedEl = embetter.utils.stringToDomElement(newEmbedHTML);
36 | embetter.utils.initPlayer(newEmbedEl, serviceObj, embetter.curEmbeds);
37 | containerEl.appendChild(newEmbedEl);
38 | // show embed code
39 | var newEmbedCode = embetter.utils.playerCode(newEmbedHTML);
40 | var newEmbedCodeEl = embetter.utils.stringToDomElement(newEmbedCode);
41 | containerEl.appendChild(newEmbedCodeEl);
42 | };
43 |
44 | embetter.utils.copyPropsToObject = function(destObj, sourceObj) {
45 | for( var key in sourceObj ){
46 | destObj[key] = sourceObj[key];
47 | }
48 | };
49 |
50 | embetter.utils.copyPropsToObject(embetter.services.video, {
51 | buildFromText: function(videoURL, containerEl) {
52 | var thumbnail = videoURL;
53 | thumbnail = thumbnail.replace('.mp4', '-poster.jpg');
54 | thumbnail = thumbnail.replace('.mov', '-poster.jpg');
55 | thumbnail = thumbnail.replace('.m4v', '-poster.jpg');
56 | embetter.utils.embedPlayerInContainer(containerEl, this, videoURL, thumbnail, videoURL);
57 | }
58 | });
59 |
60 | embetter.utils.copyPropsToObject(embetter.services.gif, {
61 | buildFromText: function(gifURL, containerEl) {
62 | var thumbnail = gifURL;
63 | thumbnail = thumbnail.replace('.gif', '-poster.jpg');
64 | embetter.utils.embedPlayerInContainer(containerEl, this, gifURL, thumbnail, gifURL);
65 | }
66 | });
67 |
68 | embetter.utils.copyPropsToObject(embetter.services.youtube, {
69 | getData: function(id) {
70 | return 'http://img.youtube.com/vi/'+ id +'/0.jpg';
71 | },
72 | buildFromText: function(text, containerEl) {
73 | var videoId = text.match(this.regex)[1];
74 | if(videoId != null) {
75 | var videoURL = this.link(videoId);
76 | var videoThumbnail = this.getData(videoId);
77 | embetter.utils.embedPlayerInContainer(containerEl, this, videoURL, videoThumbnail, videoId);
78 | }
79 | }
80 | });
81 |
82 | embetter.utils.copyPropsToObject(embetter.services.vimeo, {
83 | getData: function(mediaUrl, callback, sampleData) {
84 | var videoId = mediaUrl.split('vimeo.com/')[1];
85 | window.reqwest({
86 | url: sampleData || 'https://vimeo.com/api/v2/video/'+ videoId +'.json',
87 | type: (sampleData) ? 'json' : 'jsonp',
88 | error: function (err) {},
89 | success: function (data) {
90 | callback(data[0].thumbnail_large);
91 | }
92 | })
93 |
94 | return '';
95 | },
96 | buildFromText: function(text, containerEl, sampleData) {
97 | var self = this;
98 | var videoId = text.match(this.regex)[1];
99 | if(videoId != null) {
100 | var videoURL = this.link(videoId);
101 | this.getData(videoURL, function(videoThumbnail) {
102 | embetter.utils.embedPlayerInContainer(containerEl, self, videoURL, videoThumbnail, videoId);
103 | }, sampleData);
104 | }
105 | }
106 | });
107 |
108 | embetter.utils.copyPropsToObject(embetter.services.soundcloud, {
109 | getData: function(mediaUrl, callback, sampleData) {
110 | reqwest({
111 | // url: 'http://soundcloud.com/oembed?url='+ mediaUrl +'&format=json',
112 | // http://soundcloud.com/oembed?url=https://soundcloud.com/cacheflowe/sets/automate-everything-2005&format=json
113 | url: sampleData || 'http://api.soundcloud.com/resolve.json?url='+ mediaUrl +'&client_id=YOUR_CLIENT_ID&callback=jsonpResponse',
114 | type: (sampleData) ? 'json' : 'jsonp',
115 | error: function (err) {},
116 | success: function (data) {
117 | callback(data);
118 | }
119 | })
120 | },
121 | largerThumbnail: function(thumbnail) {
122 | return thumbnail.replace('large.jpg', 't500x500.jpg');
123 | },
124 | buildFromText: function(text, containerEl, sampleData) {
125 | var self = this;
126 | var soundURL = this.link(text.match(this.regex)[1]);
127 | if(soundURL != null) {
128 | this.getData(soundURL, function(data) {
129 | // progressively fall back from sound image to user image to group creator image. grab larger image where possible
130 | var thumbnail = data.artwork_url;
131 | if(thumbnail) thumbnail = self.largerThumbnail(thumbnail);
132 |
133 | if(thumbnail == null) {
134 | thumbnail = (data.user) ? data.user.avatar_url : null;
135 | if(thumbnail) thumbnail = self.largerThumbnail(thumbnail);
136 | }
137 |
138 | if(thumbnail == null) {
139 | thumbnail = (data.creator) ? data.creator.avatar_url : null;
140 | if(thumbnail) thumbnail = self.largerThumbnail(thumbnail);
141 | }
142 |
143 | if(thumbnail) {
144 | // handle special soundcloud ids
145 | var soundId = data.id;
146 | if(soundURL.indexOf('/sets/') != -1) soundId = 'playlists/' + soundId;
147 | else if(soundURL.indexOf('/groups/') != -1) soundId = 'groups/' + soundId;
148 | else soundId = 'tracks/' + soundId;
149 |
150 | // create embed
151 | embetter.utils.embedPlayerInContainer(containerEl, self, soundURL, thumbnail, soundId);
152 | }
153 | }, sampleData);
154 | }
155 | }
156 | });
157 |
158 | embetter.utils.copyPropsToObject(embetter.services.instagram, {
159 | getData: function(id) {
160 | return 'https://instagram.com/p/' + id +'/media/?size=l';
161 | },
162 | buildFromText: function(text, containerEl) {
163 | var mediaId = text.match(this.regex)[1];
164 | var mediaURL = this.link(mediaId);
165 | if(mediaURL != null) {
166 | var thumbnail = this.getData(mediaId);
167 | embetter.utils.embedPlayerInContainer(containerEl, this, mediaURL, thumbnail, mediaId);
168 | }
169 | }
170 | });
171 |
172 | embetter.utils.copyPropsToObject(embetter.services.dailymotion, {
173 | getData: function(id) {
174 | return 'http://www.dailymotion.com/thumbnail/video/'+ id;
175 | },
176 | buildFromText: function(text, containerEl) {
177 | text = text.split('_')[0];
178 | var videoId = text.match(this.regex)[1];
179 | if(videoId != null) {
180 | var videoURL = this.link(videoId);
181 | var videoThumbnail = this.getData(videoId);
182 | embetter.utils.embedPlayerInContainer(containerEl, this, videoURL, videoThumbnail, videoId);
183 | }
184 | }
185 | });
186 |
187 | embetter.utils.copyPropsToObject(embetter.services.mixcloud, {
188 | getData: function(mediaUrl, callback, sampleData) {
189 | window.reqwest({
190 | url: sampleData || 'http://www.mixcloud.com/oembed/?url='+ mediaUrl +'&format=jsonp',
191 | type: (sampleData) ? 'json' : 'jsonp',
192 | error: function (err) {},
193 | success: function (data) {
194 | callback(data);
195 | }
196 | });
197 | },
198 | buildFromText: function(text, containerEl, sampleData) {
199 | var self = this;
200 | var soundId = text.match(this.regex)[1];
201 | var soundURL = this.link(soundId);
202 | if(soundURL != null) {
203 | this.getData(soundURL, function(data) {
204 | if(data.image) {
205 | embetter.utils.embedPlayerInContainer(containerEl, self, soundURL, data.image, soundId);
206 | }
207 | }, sampleData);
208 | }
209 | }
210 | });
211 |
212 | embetter.utils.copyPropsToObject(embetter.services.codepen, {
213 | getData: function(id) {
214 | return 'http://codepen.io/' + id + '/image/large.png';
215 | },
216 | buildFromText: function(text, containerEl) {
217 | var penId = text.match(this.regex)[1];
218 | penId = penId.replace('/embed/', '/pen/');
219 | if(penId != null) {
220 | var penURL = this.link(penId);
221 | var penThumbnail = this.getData(penId);
222 | embetter.utils.embedPlayerInContainer(containerEl, this, penURL, penThumbnail, penId);
223 | }
224 | }
225 | });
226 |
227 | embetter.utils.copyPropsToObject(embetter.services.bandcamp, {
228 | getData: function(bandcampURL, callback, sampleData) {
229 | window.reqwest({
230 | url: sampleData || bandcampURL,
231 | type: 'html',
232 | error: function (err) {},
233 | success: function (data) {
234 | callback(data);
235 | }
236 | })
237 | },
238 | regexForId: /((?:album|track)=[0-9]*)/,
239 | regexForThumb: /https:\/\/(.)*_16.jpg/,
240 | buildFromText: function(text, containerEl, sampleData) {
241 | var self = this;
242 | var bandcampId = text.match(this.regex)[1];
243 | if(bandcampId != null) {
244 | var bandcampURL = this.link(bandcampId);
245 | this.getData(bandcampURL, function(html) {
246 | if(html.match(self.regexForId) != null) {
247 | var streamId = html.match(self.regexForId)[0];
248 | var thumbnailUrl = html.match(self.regexForThumb)[0];
249 | embetter.utils.embedPlayerInContainer(containerEl, self, bandcampURL, thumbnailUrl, streamId);
250 | }
251 | }, sampleData);
252 | }
253 | }
254 | });
255 |
256 | embetter.utils.copyPropsToObject(embetter.services.ustream, {
257 | getData: function(mediaUrl, callback, sampleData) {
258 | window.reqwest({
259 | url: sampleData || 'https://www.ustream.tv/oembed?url='+ mediaUrl,
260 | type: 'json',
261 | error: function (err) {},
262 | success: function (data) {
263 | callback(data);
264 | }
265 | });
266 | },
267 | buildFromText: function(text, containerEl, sampleData) {
268 | var self = this;
269 | var streamId = text.match(this.regex)[1];
270 | var streamURL = this.link(streamId);
271 | if(streamURL != null) {
272 | this.getData(streamURL, function(data) {
273 | if(data.thumbnail_url) {
274 | var channelId = data.html.match(/http:\/\/www.ustream.tv\/embed\/([0-9]*)/);
275 | var recordedId = data.html.match(/http:\/\/www.ustream.tv\/embed\/recorded\/([0-9]*)/);
276 | streamId = (recordedId != null) ? 'recorded/' + recordedId[1] : channelId[1];
277 | embetter.utils.embedPlayerInContainer(containerEl, self, streamURL, data.thumbnail_url, streamId);
278 | }
279 | }, sampleData);
280 | }
281 | }
282 | });
283 |
284 | embetter.utils.copyPropsToObject(embetter.services.imgur, {
285 | getData: function(mediaUrl, callback, sampleData) {
286 | window.reqwest({
287 | // url: 'http://api.imgur.com/oembed.json?url='+ mediaUrl, // oembed URL doesn't return a thumbnail for us, so it's useless here
288 | url: sampleData || bandcampURL,
289 | type: 'html',
290 | error: function (err) {
291 | console.log('imgur error', err);
292 | },
293 | success: function (data) {
294 | callback(data);
295 | }
296 | });
297 | },
298 | buildFromText: function(text, containerEl, sampleData) {
299 | var self = this;
300 | var imgurId = text.match(this.regex)[1];
301 | if(imgurId != null) {
302 | var mediaURL = this.link(imgurId);
303 | this.getData(mediaURL, function(data) {
304 | if(data.match('content="gallery"') != null) {
305 | var thumbMatch = data.match(/image_src(?:.)*(http(.)*jpg)/);
306 | var thumbnail = thumbMatch[1];
307 | if(thumbMatch && thumbMatch.length) {
308 | // check for gallery, then prepend "/a/" before the image ID embed
309 | var embedId = imgurId.replace('gallery', 'a');
310 | embetter.utils.embedPlayerInContainer(containerEl, self, mediaURL, thumbnail, embedId);
311 | }
312 | } else if(data.match('twitter:image:src') != null) {
313 | var thumbMatch = data.match(/twitter:image:src(?:.)*(http(.)*jpg)/);
314 | var thumbnail = thumbMatch[1];
315 | if(thumbMatch && thumbMatch.length) {
316 | // if not an actual gallery, remove gallery/ from the url for a proper embed ID
317 | var embedId = imgurId.replace('gallery/', '');
318 | embetter.utils.embedPlayerInContainer(containerEl, self, mediaURL, thumbnail, embedId);
319 | }
320 | }
321 | }, sampleData);
322 | }
323 | }
324 | });
325 |
326 | embetter.utils.copyPropsToObject(embetter.services.vine, {
327 | getData: function(imgId, callback, sampleData) {
328 | window.reqwest({
329 | url: sampleData || 'https://vine.co/oembed/' + imgId + '.json',
330 | type: 'json',
331 | error: function (err) {
332 | console.log('vine error', err);
333 | },
334 | success: function (data) {
335 | callback(data);
336 | }
337 | });
338 | },
339 | buildFromText: function(text, containerEl, sampleData) {
340 | var videoId = text.match(this.regex)[1];
341 | if(videoId != null) {
342 | var self = this;
343 | this.getData(videoId, function(data) {
344 | if(data.thumbnail_url) {
345 | var vineURL = self.link(videoId);
346 | embetter.utils.embedPlayerInContainer(containerEl, self, vineURL, data.thumbnail_url, videoId);
347 | }
348 | }, sampleData);
349 | }
350 | }
351 | });
352 |
353 | embetter.utils.copyPropsToObject(embetter.services.slideshare, {
354 | getData: function(imgId, callback, sampleData) {
355 | window.reqwest({
356 | url: sampleData || 'http://www.slideshare.net/api/oembed/2?url=https://www.slideshare.net/' + imgId + '&format=json',
357 | type: 'json',
358 | error: function (err) {},
359 | success: function (data) {
360 | callback(data);
361 | }
362 | });
363 | },
364 | buildFromText: function(text, containerEl, sampleData) {
365 | var videoId = text.match(this.regex)[1];
366 | if(videoId != null) {
367 | var self = this;
368 | this.getData(videoId, function(data) {
369 | if(data.thumbnail) {
370 | var imgId = data.html.match(/embed_code\/key\/([a-zA-Z0-9\-\/]*)/)[1];
371 | var slideshareURL = self.link(videoId);
372 | embetter.utils.embedPlayerInContainer(containerEl, self, slideshareURL, data.thumbnail, imgId);
373 | }
374 | }, sampleData);
375 | }
376 | }
377 | });
378 |
379 | embetter.utils.copyPropsToObject(embetter.services.giphy, {
380 | getData: function(id) {
381 | return 'https://media.giphy.com/media/' + id + '/giphy_s.gif';
382 | },
383 | buildFromText: function(text, containerEl, sampleData) {
384 | var self = this;
385 | var splitPath = text.split('/');
386 | var longId = splitPath[splitPath.length - 1]; // get id, with extra dashed data
387 | var dashedId = longId.split('-');
388 | var giphyId = dashedId[dashedId.length - 1]; // get id without extra dashed data
389 | if(giphyId != null) {
390 | var giphyURL = this.link(longId);
391 | var thumbnailUrl = this.getData(giphyId);
392 | var gifURL = 'https://media.giphy.com/media/' + giphyId + '/giphy.gif'; // not used for now
393 | embetter.utils.embedPlayerInContainer(containerEl, self, giphyURL, thumbnailUrl, giphyId);
394 | }
395 | }
396 | });
397 |
398 | embetter.utils.copyPropsToObject(embetter.services.shadertoy, {
399 | getData: function(id) {
400 | return 'https://www.shadertoy.com/media/shaders/'+id+'.jpg';
401 | },
402 | buildFromText: function(text, containerEl) {
403 | var shaderId = text.match(this.regex)[1];
404 | if(shaderId != null) {
405 | var shaderURL = this.link(shaderId);
406 | var shaderThumbnail = this.getData(shaderId);
407 | embetter.utils.embedPlayerInContainer(containerEl, this, shaderURL, shaderThumbnail, shaderId);
408 | }
409 | }
410 | });
411 |
412 | embetter.utils.copyPropsToObject(embetter.services.kuula, {
413 | getData: function(id) {
414 | return 'https://kuula.co/cover/'+id;
415 | },
416 | buildFromText: function(text, containerEl) {
417 | var postId = text.match(this.regex)[1];
418 | if(postId != null) {
419 | var postURL = this.link(postId);
420 | var postThumbnail = this.getData(postId);
421 | embetter.utils.embedPlayerInContainer(containerEl, this, postURL, postThumbnail, postId);
422 | }
423 | }
424 | });
425 |
426 | })();
427 |
--------------------------------------------------------------------------------
/embetter.css:
--------------------------------------------------------------------------------
1 | .embetter {
2 | -webkit-transition: background-color 0.25s linear, max-width 0.25s linear, max-height 0.25s linear;
3 | -moz-transition: background-color 0.25s linear, max-width 0.25s linear, max-height 0.25s linear;
4 | -ms-transition: background-color 0.25s linear, max-width 0.25s linear, max-height 0.25s linear;
5 | -o-transition: background-color 0.25s linear, max-width 0.25s linear, max-height 0.25s linear;
6 | transition: background-color 0.25s linear, max-width 0.25s linear, max-height 0.25s linear;
7 |
8 | background-color: transparent;
9 | position: relative;
10 | display: block;
11 | overflow: hidden;
12 | }
13 |
14 | .embetter:hover {
15 | background-color: #000;
16 | }
17 |
18 | .embetter a {
19 | display: block;
20 | line-height: 0;
21 | margin: 0;
22 | }
23 |
24 | .embetter img {
25 | -webkit-transition: opacity 0.25s linear, padding 0.25s linear, max-width 0.25s linear, -webkit-transform 0.25s linear;
26 | -moz-transition: opacity 0.25s linear, padding 0.25s linear, max-width 0.25s linear, -moz-transform 0.25s linear;
27 | -ms-transition: opacity 0.25s linear, padding 0.25s linear, max-width 0.25s linear, -ms-transform 0.25s linear;
28 | -o-transition: opacity 0.25s linear, padding 0.25s linear, max-width 0.25s linear, -o-transform 0.25s linear;
29 | transition: opacity 0.25s linear, padding 0.25s linear, max-width 0.25s linear, transform 0.25s linear;
30 |
31 | width: 100%;
32 | margin: 0;
33 | }
34 |
35 | .embetter:hover img {
36 | opacity: 0.9;
37 | -webkit-transform: scale(1.02);
38 | -moz-transform: scale(1.02);
39 | -ms-transform: scale(1.02);
40 | -o-transform: scale(1.02);
41 | transform: scale(1.02);
42 | }
43 |
44 | .embetter.embetter-static:hover img {
45 | opacity: 1;
46 | -webkit-transform: none;
47 | -moz-transform: none;
48 | -ms-transform: none;
49 | -o-transform: none;
50 | transform: none;
51 | }
52 |
53 | .embetter.embetter-playing img {
54 | opacity: 0;
55 | }
56 |
57 |
58 | .embetter .embetter-play-button,
59 | .embetter .embetter-loading {
60 | -webkit-transition: opacity 0.25s linear;
61 | -moz-transition: opacity 0.25s linear;
62 | -ms-transition: opacity 0.25s linear;
63 | -o-transition: opacity 0.25s linear;
64 | transition: opacity 0.25s linear;
65 | }
66 |
67 | .embetter .embetter-play-button,
68 | .embetter .embetter-loading {
69 | position: absolute;
70 | top: 0;
71 | left: 0;
72 | width: 100%;
73 | height: 100%;
74 | overflow: hidden;
75 | cursor: pointer;
76 | }
77 |
78 | .embetter.embetter-playing .embetter-play-button {
79 | opacity: 0;
80 | }
81 |
82 | .embetter .embetter-play-button:before {
83 | background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2286%22%20height%3D%2260%22%20viewBox%3D%220%200%2086%2060%22%3E%3Cpath%20fill%3D%22%23010101%22%20d%3D%22M0%200h86v60h-86z%22/%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M35.422%2017.6v24.8l22.263-12.048z%22/%3E%3C/svg%3E');
84 | /* */
85 | background-repeat: no-repeat;
86 | background-position: 50% 50%;
87 | background-size: 33.333% auto;
88 | width: 100%;
89 | max-width: 258px;
90 | height: 100%;
91 | min-height: 100%;
92 | content: " ";
93 | margin: 0 auto;
94 | display: block;
95 | }
96 |
97 | /* Audio services have a round play button */
98 | .embetter[data-soundcloud-id] div:before,
99 | .embetter[data-mixcloud-id] div:before {
100 | background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2265%22%20height%3D%2265%22%20viewBox%3D%220%200%2065%2065%22%3E%3Ccircle%20fill%3D%22%23010101%22%20cx%3D%2232.5%22%20cy%3D%2232.5%22%20r%3D%2232.5%22/%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M25.095%2020.932v23.136l20.769-11.24z%22/%3E%3C/svg%3E');
101 | /* */
102 | max-width: 195px;
103 | }
104 |
105 | .embetter .embetter-loading {
106 | background-color: #000000;
107 | opacity: 0;
108 | }
109 |
110 | .embetter.embetter-playing .embetter-loading {
111 | opacity: 1;
112 | }
113 |
114 | .embetter .embetter-loading:before {
115 | background-repeat: no-repeat;
116 | background-position: 51.7% 50%;
117 | background-size: 9.0909% auto; /* 1/11th of the max width for a background-size of 23px 25px */
118 | max-width: 253px;
119 | width: 100%;
120 | height: 100%;
121 | min-height: 100%;
122 | content: " ";
123 | margin: 0 auto;
124 | display: block;
125 | }
126 |
127 | .embetter.embetter-playing .embetter-loading:before {
128 | background-image: url("data:image/gif;base64,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");
129 | }
130 |
131 | .embetter iframe {
132 | position: absolute;
133 | top: 0;
134 | left: 0;
135 | width: 100%;
136 | height: 100%;
137 | }
138 |
139 |
140 |
141 | /* Per-service overrides */
142 |
143 | .embetter[data-youtube-id],
144 | .embetter[data-dailymotion-id] {
145 | padding-bottom: 56.25%;
146 | height: 0;
147 | }
148 |
149 | .embetter[data-youtube-id] img {
150 | margin: -9.4% 0;
151 | }
152 |
153 | .embetter[data-soundcloud-id],
154 | .embetter[data-bandcamp-id],
155 | .embetter[data-vine-id] {
156 | max-width: 600px;
157 | }
158 |
159 | .embetter[data-mixcloud-id] {
160 | max-width: 600px;
161 | max-height: 600px;
162 | }
163 | .embetter[data-mixcloud-id].embetter-playing {
164 | max-width: 660px;
165 | max-height: 180px;
166 | }
167 |
168 | .embetter[data-codepen-id] {
169 | max-width: 700px;
170 | }
171 |
172 | .embetter[data-ustream-id] {
173 | max-width: 640px;
174 | }
175 |
176 | .embetter[data-slideshare-id] {
177 | max-width: 1080px;
178 | }
179 |
180 | .embetter[data-imgur-id] {
181 | max-width: 540px;
182 | }
183 |
184 | .embetter[data-instagram-id] {
185 | max-width: 640px;
186 | }
187 | .embetter[data-instagram-id].embetter-playing {
188 | max-width: 658px;
189 | }
190 | .embetter[data-instagram-id].embetter-playing img {
191 | padding: 32px 0 48px 0;
192 | }
193 |
194 |
195 |
--------------------------------------------------------------------------------
/embetter.js:
--------------------------------------------------------------------------------
1 | (function(){
2 |
3 | window.embetter = {};
4 | var embetter = window.embetter;
5 |
6 | /////////////////////////////////////////////////////////////
7 | // COMMON UTIL HELPERS
8 | /////////////////////////////////////////////////////////////
9 |
10 | embetter.debug = true;
11 | embetter.curEmbeds = [];
12 | embetter.mobileScrollTimeout = null;
13 | embetter.mobileScrollSetup = false;
14 | embetter.apiEnabled = false;
15 | embetter.apiAutoplayCallback = null;
16 | embetter.defaultThumbnail = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAArwAAAGcAQMAAAABMOGrAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAADUExURQAAAKd6PdoAAAA6SURBVHja7cGBAAAAAMOg+VPf4ARVAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAN488AAGP4e1mAAAAAElFTkSuQmCC';
17 |
18 | embetter.utils = {
19 | /////////////////////////////////////////////////////////////
20 | // REGEX HELPERS
21 | /////////////////////////////////////////////////////////////
22 | buildRegex: function(regexStr) {
23 | var optionalPrefix = '(?:https?:\\/\\/)?(?:w{3}\\.)?';
24 | var terminator = '(?:\\/?|$|\\s|\\?|#)';
25 | return new RegExp(optionalPrefix + regexStr + terminator);
26 | },
27 | /////////////////////////////////////////////////////////////
28 | // BUILD HTML TEMPLATES
29 | /////////////////////////////////////////////////////////////
30 | stringToDomElement: function(str) {
31 | var div = document.createElement('div');
32 | div.innerHTML = str;
33 | return div.firstChild;
34 | },
35 | playerHTML: function(service, mediaUrl, thumbnail, id) {
36 | return '\
37 |
\
38 |
';
39 | },
40 | isMobile: (function() {
41 | return navigator.userAgent.toLowerCase().match(/iphone|ipad|ipod|android/) ? true : false;
42 | })(),
43 | matches: (function() {
44 | var b = document.createElement('div');
45 | return b.matches || b.webkitMatchesSelector || b.mozMatchesSelector || b.msMatchesSelector;
46 | })(),
47 | parentSelector: function(node, selector) {
48 | if (this.matches.bind(node)(selector)) {
49 | return node;
50 | }
51 | node = node.parentNode;
52 | while (node && node !== document) {
53 | if (this.matches.bind(node)(selector)) {
54 | return node;
55 | } else {
56 | node = node.parentNode;
57 | }
58 | }
59 | return false;
60 | },
61 |
62 | /////////////////////////////////////////////////////////////
63 | // MEDIA PLAYERS PAGE MANAGEMENT
64 | /////////////////////////////////////////////////////////////
65 | initMediaPlayers: function(el, services) {
66 | for (var i = 0; i < services.length; i++) {
67 | var service = services[i];
68 | var serviceEmbedContainers = el.querySelectorAll('div['+service.dataAttribute+']');
69 | for(var j=0; j < serviceEmbedContainers.length; j++) {
70 | embetter.utils.initPlayer(serviceEmbedContainers[j], service);
71 | }
72 | }
73 | // handle mobile auto-embed on scroll
74 | if(embetter.utils.isMobile && embetter.mobileScrollSetup == false) {
75 | window.addEventListener('scroll', embetter.utils.scrollListener);
76 | embetter.mobileScrollSetup = true;
77 | // force scroll to trigger listener on page load
78 | window.scroll(window.scrollX, window.scrollY+1);
79 | window.scroll(window.scrollX, window.scrollY-1);
80 | };
81 | },
82 | scrollListener: function() {
83 | // throttled scroll listener
84 | if(embetter.mobileScrollTimeout != null) {
85 | window.clearTimeout(embetter.mobileScrollTimeout);
86 | }
87 | // check to see if embeds are on screen. if so, embed! otherwise, unembed
88 | // exclude codepen since we don't know what might execute
89 | embetter.mobileScrollTimeout = setTimeout(function() {
90 | for (var i = 0; i < embetter.curEmbeds.length; i++) {
91 | var player = embetter.curEmbeds[i];
92 | var playerRect = player.el.getBoundingClientRect();
93 | if(playerRect.top < window.innerHeight && playerRect.bottom > 0) {
94 | if(player.getType() !== 'codepen') { // && player.getType() != 'gif'
95 | player.embedMedia(false);
96 | }
97 | } else {
98 | player.unembedMedia();
99 | }
100 | };
101 | }, 500);
102 | },
103 | initPlayer: function(embedEl, service) {
104 | if(embedEl.classList.contains('embetter-ready') == true) return;
105 | if(embedEl.classList.contains('embetter-static') == true) return;
106 | embetter.curEmbeds.push( new embetter.EmbetterPlayer(embedEl, service) );
107 | },
108 | unembedPlayers: function(containerEl) {
109 | for (var i = 0; i < embetter.curEmbeds.length; i++) {
110 | if(containerEl && containerEl.contains(embetter.curEmbeds[i].el)) {
111 | embetter.curEmbeds[i].unembedMedia();
112 | }
113 | };
114 | },
115 | disposePlayers: function() {
116 | for (var i = 0; i < embetter.curEmbeds.length; i++) {
117 | embetter.curEmbeds[i].dispose();
118 | }
119 | window.removeEventListener('scroll', embetter.utils.scrollListener);
120 | embetter.mobileScrollSetup = false;
121 | embetter.curEmbeds.splice(0, embetter.curEmbeds.length-1);
122 | },
123 | mediaComplete: function() {
124 | if(embetter.curPlayer !== null) {
125 | var playerEl = embetter.curPlayer.el;
126 | var playlistContainer = this.parentSelector(playerEl, '[data-embetter-playlist]'); // check if we're in a playlist container
127 | if(playlistContainer) {
128 | var playlistPlayerEls = playlistContainer.querySelectorAll('.embetter');
129 | for(var i=0; i < playlistPlayerEls.length - 1; i++) { // skip the last one, since there's nothing else to play
130 | if(playlistPlayerEls[i].classList.contains('embetter-playing')) { // find the active player and tell the next one to play
131 | var nextPlayerObj = embetter.utils.getPlayerFromEl(playlistPlayerEls[i+1]);
132 | if(nextPlayerObj) {
133 | nextPlayerObj.play();
134 | if(embetter.apiAutoplayCallback) embetter.apiAutoplayCallback(nextPlayerObj.el);
135 | }
136 | break;
137 | }
138 | }
139 | }
140 | }
141 | },
142 | getPlayerFromEl: function(el) {
143 | for (var i=0; i < embetter.curEmbeds.length; i++) {
144 | if(el === embetter.curEmbeds[i].el) {
145 | return embetter.curEmbeds[i];
146 | }
147 | }
148 | return null;
149 | },
150 | disposeDetachedPlayers: function() {
151 | // dispose any players no longer in the DOM
152 | for (var i = embetter.curEmbeds.length - 1; i >= 0; i--) {
153 | var embed = embetter.curEmbeds[i];
154 | if(document.body.contains(embed.el) === false || embed.el === null) {
155 | embed.dispose();
156 | delete embetter.curEmbeds.splice(i,1);
157 | }
158 | }
159 | },
160 | loadRemoteScript: function(scriptURL) {
161 | var tag = document.createElement('script');
162 | tag.src = scriptURL;
163 | var firstScriptTag = document.getElementsByTagName('script')[0];
164 | firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
165 | }
166 | };
167 |
168 |
169 | /////////////////////////////////////////////////////////////
170 | // 3RD-PARTY SERVICE SUPPORT
171 | /////////////////////////////////////////////////////////////
172 |
173 | embetter.services = {};
174 |
175 | /////////////////////////////////////////////////////////////
176 | // NATIVE VIDEO
177 | /////////////////////////////////////////////////////////////
178 | embetter.services.video = {
179 | type: 'video',
180 | dataAttribute: 'data-video-url',
181 | regex: embetter.utils.buildRegex('(.mp4|.mov|.m4v)'),
182 | embed: function (player) { // this.id, this.thumbnail.width, this.thumbnail.height, this.autoplay, this.thumbnail.src
183 | var autoplayAttr = (player.autoplay == true) ? ' autoplay="true" ' : '';
184 | var loopsAttr = (player.loops == true) ? ' loop="true" ' : '';
185 | return '';
186 | },
187 | };
188 |
189 | /////////////////////////////////////////////////////////////
190 | // GIF FILE
191 | /////////////////////////////////////////////////////////////
192 | embetter.services.gif = {
193 | type: 'gif',
194 | dataAttribute: 'data-gif-url',
195 | regex: embetter.utils.buildRegex('.gif'),
196 | embed: function(player) {
197 | return ' ';
198 | },
199 | };
200 |
201 | /////////////////////////////////////////////////////////////
202 | // YOUTUBE
203 | // http://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
204 | // https://developers.google.com/youtube/iframe_api_reference
205 | // http://stackoverflow.com/questions/3717115/regular-expression-for-youtube-links
206 | /////////////////////////////////////////////////////////////
207 | embetter.services.youtube = {
208 | type: 'youtube',
209 | dataAttribute: 'data-youtube-id',
210 | regex: /(?:.+?)?(?:youtube\.com\/v\/|watch\/|\?v=|\&v=|youtu\.be\/|\/v=|^youtu\.be\/)([a-zA-Z0-9_-]{11})+/,
211 | embed: function(player) {
212 | var autoplayQuery = (player.autoplay === true) ? '&autoplay=1' : '';
213 | return 'VIDEO ';
214 | },
215 | link: function(id) {
216 | return 'https://www.youtube.com/watch?v=' + id;
217 | },
218 | loadAPI: function(apiLoadedCallback) {
219 | var self = this;
220 | if(typeof window.onYouTubeIframeAPIReady !== 'undefined') {
221 | apiLoadedCallback();
222 | self.activateCurrentPlayer();
223 | return;
224 | }
225 | // docs here: https://developers.google.com/youtube/iframe_api_reference
226 | // requires enablejsapi above to connect to an existing iframe
227 | // load the IFrame Player API code asynchronously.
228 | embetter.utils.loadRemoteScript("https://www.youtube.com/iframe_api");
229 | // creates an