This repository has been archived by the owner on May 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
2,980 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
.DS_Store | ||
|
||
# Logs | ||
logs | ||
*.log | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,51 @@ | ||
# youtube-video-element | ||
# `<youtube-video>` | ||
A custom element (web component) for the YouTube player. | ||
|
||
The element API matches the HTML5 `<video>` tag, so it can be easily swapped with other media, and be compatible with other UI components that work with the video tag. | ||
|
||
## Example | ||
|
||
```html | ||
<html> | ||
<head> | ||
<script type="module" src="https://unpkg.com/youtube-video-element"></script> | ||
</head> | ||
<body> | ||
|
||
<youtube-video controls src="https://www.youtube.com/watch?v=rubNgGj3pYo"></youtube-video> | ||
|
||
</body> | ||
</html> | ||
``` | ||
|
||
## Installing | ||
|
||
`<youtube-video>` is packaged as a javascript module (es6) only, which is supported by all evergreen browsers and Node v12+. | ||
|
||
### Loading into your HTML using `<script>` | ||
|
||
Note the `type="module"`, that's important. | ||
|
||
> Modules are always loaded asynchronously by the browser, so it's ok to load them in the head :thumbsup:, and best for registering web components quickly. | ||
```html | ||
<head> | ||
<script type="module" src="https://unpkg.com/youtube-video-element"></script> | ||
</head> | ||
``` | ||
|
||
### Adding to your app via `npm` | ||
|
||
```bash | ||
npm install youtube-video-element --save | ||
``` | ||
Or yarn | ||
```bash | ||
yarn add youtube-video-element | ||
``` | ||
|
||
Include in your app javascript (e.g. src/App.js) | ||
```js | ||
import 'player-chrome'; | ||
``` | ||
This will register the custom elements with the browser so they can be used as HTML. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<html> | ||
<head> | ||
<script type="module" src="./index.js"></script> | ||
</head> | ||
<body> | ||
<youtube-video controls src="https://www.youtube.com/watch?v=rubNgGj3pYo"></youtube-video> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
// Build the HTML/CSS of the element | ||
const template = document.createElement('template'); | ||
|
||
template.innerHTML = ` | ||
<style> | ||
:host { | ||
display: inline-block; | ||
box-sizing: border-box; | ||
position: relative; | ||
width: 640px; | ||
height: 360px; | ||
background-color: #000; | ||
} | ||
iframe, | ||
iframeContainer { | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
width: 100%; | ||
height: 100%; | ||
border: none; | ||
overflow: hidden; | ||
} | ||
</style> | ||
<div id="iframeContainer"></div> | ||
`; | ||
|
||
function getIframeTemplate(params) { | ||
const { id } = params; | ||
const controls = params.controls ? 1 : 0; | ||
const template = document.createElement('template'); | ||
|
||
template.innerHTML = ` | ||
<iframe | ||
id="player" | ||
type="text/html" | ||
src="https://www.youtube.com/embed/${id}?enablejsapi=1&modestbranding=1&rel=0&showinfo=0&controls=${controls}" | ||
frameborder="0" | ||
allowfullscreen | ||
allow="accelerometer; autoplay; encrypted-media; fullscreen; gyroscope; picture-in-picture; xr-spatial-tracking" | ||
></iframe> | ||
`; | ||
|
||
return template; | ||
} | ||
|
||
/* | ||
This video had an issue where it would start to play but then go back to paused. | ||
Wondering if it's some sort of playlist issue? | ||
https://www.youtube.com/watch?v=M7lc1UVf-VE | ||
*/ | ||
|
||
function getIdFromURL(url) { | ||
const regExp = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/; | ||
const match = url.match(regExp); | ||
return (match && match[1]) ? match[1] : url; | ||
} | ||
|
||
class YoutubeVideoElement extends HTMLElement { | ||
constructor() { | ||
super(); | ||
|
||
this.attachShadow({ mode: 'open' }); | ||
this.shadowRoot.appendChild(template.content.cloneNode(true)); | ||
|
||
const src = this.getAttribute('src'); | ||
|
||
if (src) { | ||
this.load(); | ||
} | ||
} | ||
|
||
// Handle multiple players with one YT API load | ||
static ytReady = false; | ||
static ytReadyQueue = []; | ||
static onYTReady = function(callback) { | ||
if (this.ytReady) { | ||
callback(); | ||
} else { | ||
this.ytReadyQueue.push(callback); | ||
} | ||
}; | ||
static handleYoutubeAPILoad() { | ||
this.ytReady = true; | ||
this.ytReadyQueue.forEach((callback) => { | ||
callback(); | ||
}); | ||
this.ytReadyQueue = []; | ||
} | ||
|
||
load() { | ||
// Destroy previous videos | ||
this.ytPlayer = null; | ||
this.shadowRoot.querySelector('#iframeContainer').innerHTML = ''; | ||
|
||
const src = this.getAttribute('src'); | ||
|
||
if (!src) { | ||
// Should throw an code 4 error. We'll get ot that. | ||
console.error('YoutubeVideoElement: No src was set when load() was called.'); | ||
return; | ||
} | ||
|
||
const iframeTemplate = getIframeTemplate({ | ||
id: getIdFromURL(src), | ||
controls: !!this.hasAttribute('controls') | ||
}); | ||
|
||
this.shadowRoot.querySelector('#iframeContainer').appendChild(iframeTemplate.content.cloneNode(true)); | ||
const iframe = this.shadowRoot.querySelector('iframe'); | ||
|
||
YoutubeVideoElement.onYTReady(()=>{ | ||
const onPlayerReady = (event) => { | ||
this.dispatchEvent(new Event('volumechange')); | ||
|
||
this.timeupdateInterval = setInterval(()=>{ | ||
this.dispatchEvent(new Event('timeupdate')); | ||
}, 25); | ||
} | ||
|
||
const onPlayerStateChange = (event) => { | ||
const state = event.data; | ||
|
||
if (state == 1) { | ||
this.dispatchEvent(new Event('play')); | ||
} else if (state == 2) { | ||
this.dispatchEvent(new Event('pause')); | ||
} | ||
} | ||
|
||
const onPlayerError = (event) => { | ||
console.log('onPlayerError', event.data, event); | ||
} | ||
|
||
this.ytPlayer = new YT.Player(iframe, { | ||
events: { | ||
'onReady': onPlayerReady, | ||
'onStateChange': onPlayerStateChange, | ||
'onError': onPlayerError | ||
} | ||
}); | ||
|
||
this.ytPlayer.addEventListener('onPlaybackRateChange', e => { | ||
this.dispatchEvent(new Event('ratechange')); | ||
}); | ||
}); | ||
} | ||
|
||
connectedCallback() { | ||
} | ||
|
||
/* onStateChange | ||
-1 (unstarted) | ||
0 (ended) | ||
1 (playing) | ||
2 (paused) | ||
3 (buffering) | ||
5 (video cued). | ||
*/ | ||
|
||
get paused() { | ||
return !!([-1,0,2,5].indexOf(this.ytPlayer.getPlayerState()) > -1); | ||
} | ||
|
||
play() { | ||
this.ytPlayer.playVideo(); | ||
} | ||
|
||
pause() { | ||
this.ytPlayer.pauseVideo(); | ||
} | ||
|
||
get currentTime() { | ||
return this.ytPlayer.getCurrentTime(); | ||
} | ||
|
||
set currentTime(timeInSeconds) { | ||
// allowSeekAhead is true here,though should technically be false | ||
// when scrubbing w/ thumbnail previews | ||
this.ytPlayer.seekTo(timeInSeconds, true); | ||
this.dispatchEvent(new Event('timeupdate')); | ||
} | ||
|
||
get muted() { | ||
if (this.ytPlayer) { | ||
return this.ytPlayer.isMuted(); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
set muted(mute) { | ||
if (mute) { | ||
this.ytPlayer.mute() | ||
} else { | ||
this.ytPlayer.unMute() | ||
} | ||
|
||
// Leave time for post message API to update | ||
setTimeout(() => { | ||
this.dispatchEvent(new Event('volumechange')); | ||
}, 100); | ||
} | ||
|
||
get volume() { | ||
if (this.ytPlayer) { | ||
return this.ytPlayer.getVolume() / 100; | ||
} | ||
|
||
return 1; | ||
} | ||
|
||
set volume(volume) { | ||
this.ytPlayer.setVolume(volume * 100); | ||
|
||
// Leave time for post message API to update | ||
setTimeout(() => { | ||
this.dispatchEvent(new Event('volumechange')); | ||
}, 100); | ||
} | ||
|
||
get duration() { | ||
return this.ytPlayer.getDuration(); | ||
} | ||
|
||
get poster() { | ||
const id = getIdFromURL(this.src); | ||
|
||
if (id) { | ||
// https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api?page=1&tab=votes#tab-top | ||
return `https://i.ytimg.com/vi/${id}/maxresdefault.jpg`; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
get playbackRate() { | ||
return this.ytPlayer.getPlaybackRate(); | ||
} | ||
|
||
set playbackRate(rate) { | ||
this.ytPlayer.setPlaybackRate(rate); | ||
} | ||
} | ||
|
||
function loadYoutubeAPI() { | ||
if (window.onYouTubeIframeAPIReady) { | ||
console.warn('YoutubeVideoElement: onYouTubeIframeAPIReady already defined. Overwriting.'); | ||
} | ||
|
||
const YouTubeScriptTag = document.createElement('script'); | ||
YouTubeScriptTag.src = 'https://www.youtube.com/iframe_api'; | ||
const firstScriptTag = document.getElementsByTagName('script')[0]; | ||
firstScriptTag.parentNode.insertBefore(YouTubeScriptTag, firstScriptTag); | ||
|
||
window.onYouTubeIframeAPIReady = YoutubeVideoElement.handleYoutubeAPILoad.bind(YoutubeVideoElement); | ||
} | ||
|
||
if (window.customElements.get('youtube-video') || window.YoutubeVideoElement) { | ||
console.debug('YoutubeVideoElement: <youtube-video> defined more than once.'); | ||
} else { | ||
window.YoutubeVideoElement = YoutubeVideoElement; | ||
window.customElements.define('youtube-video', YoutubeVideoElement); | ||
loadYoutubeAPI(); | ||
} | ||
|
||
export default YoutubeVideoElement; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{ | ||
"name": "youtube-video-element", | ||
"version": "0.0.0", | ||
"description": "Custom element (web component) for the YouTube player.", | ||
"type": "module", | ||
"main": "dist/youtube-video-element.js", | ||
"files": [ | ||
"./dist/*", | ||
"README.md" | ||
], | ||
"scripts": { | ||
"build": "rm -rf dist && rollup --config", | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"publish-patch": "yarn run build && np patch --no-tests" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/muxinc/youtube-video-element.git" | ||
}, | ||
"publishConfig": { | ||
"registry": "https://registry.npmjs.org" | ||
}, | ||
"keywords": [ | ||
"youtube", | ||
"video", | ||
"player", | ||
"web component", | ||
"custom element" | ||
], | ||
"author": "@muxinc", | ||
"license": "MIT", | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^11.0.2", | ||
"@rollup/plugin-node-resolve": "^7.1.1", | ||
"np": "^6.2.0", | ||
"rollup": "^2.2.0", | ||
"rollup-plugin-babel-minify": "^10.0.0", | ||
"rollup-plugin-terser": "^5.3.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import resolve from '@rollup/plugin-node-resolve'; | ||
import commonjs from '@rollup/plugin-commonjs'; | ||
import { terser } from "rollup-plugin-terser"; | ||
import pkg from './package.json' | ||
|
||
export default { | ||
input: 'index.js', | ||
output: { | ||
file: pkg.main, | ||
format: 'es' | ||
}, | ||
plugins: [ | ||
resolve(), | ||
commonjs(), | ||
terser(), | ||
] | ||
}; |
Oops, something went wrong.