@u-wave/react-youtube
Advanced tools
Comparing version 0.7.2 to 1.0.0-alpha.0
module.exports = (api) => { | ||
api.cache.never(); | ||
const isTest = api.caller((caller) => caller.name === '@babel/register'); | ||
const envOptions = { | ||
modules: false, | ||
loose: true, | ||
}; | ||
if (process.env.NODE_ENV === 'cjs') { | ||
envOptions.modules = 'commonjs'; | ||
} | ||
if (process.env.NODE_ENV === 'test') { | ||
envOptions.modules = 'commonjs'; | ||
envOptions.targets = { node: 'current' }; | ||
} | ||
return { | ||
targets: isTest ? { node: 'current' } : {}, | ||
presets: [ | ||
['@babel/env', envOptions], | ||
['@babel/env', { | ||
modules: isTest ? 'commonjs' : false, | ||
}], | ||
'@babel/react', | ||
@@ -22,0 +11,0 @@ ], |
module.exports = { | ||
extends: 'airbnb', | ||
parserOptions: { | ||
ecmaVersion: 2021, | ||
}, | ||
rules: { | ||
@@ -10,2 +13,7 @@ // I disagree | ||
'react/state-in-constructor': 'off', | ||
// I disagree | ||
'react/function-component-definition': ['error', { | ||
namedComponents: 'function-declaration', | ||
unnamedComponents: 'arrow-function', | ||
}], | ||
'import/no-extraneous-dependencies': ['error', { devDependencies: true }], | ||
@@ -12,0 +20,0 @@ 'jsx-a11y/label-has-for': ['error', { |
@@ -7,2 +7,7 @@ # @u-wave/react-youtube change log | ||
## 1.0.0-alpha.0 - 2021-12-01 | ||
* Use hooks internally. | ||
* Drop support for React 16. This version requires React 17 or 18. | ||
* Target evergreen browsers. If you need to support older browsers, you need to transpile this dependency. | ||
## 0.7.2 - 2020-10-21 | ||
@@ -9,0 +14,0 @@ * Allow React 17 in peerDependency range. |
@@ -1,2 +0,2 @@ | ||
import { createElement, Component } from 'react'; | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
@@ -8,5 +8,15 @@ import loadScript from 'load-script2'; | ||
subClass.prototype.constructor = subClass; | ||
subClass.__proto__ = superClass; | ||
_setPrototypeOf(subClass, superClass); | ||
} | ||
function _setPrototypeOf(o, p) { | ||
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { | ||
o.__proto__ = p; | ||
return o; | ||
}; | ||
return _setPrototypeOf(o, p); | ||
} | ||
function _assertThisInitialized(self) { | ||
@@ -317,3 +327,3 @@ if (self === void 0) { | ||
style = _this$props3.style; | ||
return /*#__PURE__*/createElement("div", { | ||
return /*#__PURE__*/React.createElement("div", { | ||
id: id, | ||
@@ -327,3 +337,3 @@ className: className, | ||
return YouTube; | ||
}(Component); | ||
}(React.Component); | ||
@@ -552,2 +562,2 @@ if (process.env.NODE_ENV !== 'production') { | ||
export default YouTube; | ||
export { YouTube as default }; |
@@ -7,50 +7,38 @@ 'use strict'; | ||
var PropTypes = require('prop-types'); | ||
var loadScript = require('load-script2'); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React); | ||
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); | ||
var loadScript__default = /*#__PURE__*/_interopDefaultLegacy(loadScript); | ||
function _inheritsLoose(subClass, superClass) { | ||
subClass.prototype = Object.create(superClass.prototype); | ||
subClass.prototype.constructor = subClass; | ||
subClass.__proto__ = superClass; | ||
} | ||
function _assertThisInitialized(self) { | ||
if (self === void 0) { | ||
throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); | ||
} | ||
return self; | ||
} | ||
var eventNames = ['onReady', 'onStateChange', 'onPlaybackQualityChange', 'onPlaybackRateChange', 'onError', 'onApiChange']; | ||
/* global window */ | ||
/* global window, document */ | ||
function loadSdk() { | ||
return new Promise(function (resolve, reject) { | ||
return new Promise((resolve, reject) => { | ||
if (typeof window.YT === 'object' && typeof window.YT.ready === 'function') { | ||
// A YouTube SDK is already loaded, so reuse that | ||
window.YT.ready(function () { | ||
resolve(window.YT); | ||
}); | ||
window.YT.ready(resolve); | ||
return; | ||
} | ||
loadScript__default['default']('https://www.youtube.com/iframe_api', function (err) { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
window.YT.ready(function () { | ||
resolve(window.YT); | ||
}); | ||
} | ||
}); | ||
const script = document.createElement('script'); | ||
script.async = true; | ||
script.src = 'https://www.youtube.com/iframe_api'; | ||
script.onload = () => { | ||
script.onerror = null; | ||
script.onload = null; | ||
window.YT.ready(resolve); | ||
}; | ||
script.onerror = () => { | ||
script.onerror = null; | ||
script.onload = null; | ||
reject(new Error('Could not load YouTube SDK')); | ||
}; | ||
document.head.appendChild(script); | ||
}); | ||
} | ||
var sdk = null; | ||
let sdk = null; | ||
function getSdk() { | ||
@@ -64,76 +52,117 @@ if (!sdk) { | ||
var YouTube = /*#__PURE__*/function (_React$Component) { | ||
_inheritsLoose(YouTube, _React$Component); | ||
// @ts-check | ||
const { | ||
useCallback, | ||
useEffect, | ||
useRef, | ||
useState | ||
} = React__default["default"]; | ||
/** | ||
* @template {keyof YT.Events} K | ||
* @param {YT.Player} player | ||
* @param {K} event | ||
* @param {YT.Events[K]} handler | ||
*/ | ||
function YouTube(props) { | ||
var _this; | ||
function useEventHandler(player, event, handler) { | ||
useEffect(() => { | ||
if (handler) { | ||
player === null || player === void 0 ? void 0 : player.addEventListener(event, handler); | ||
} | ||
_this = _React$Component.call(this, props) || this; | ||
_this.onPlayerReady = _this.onPlayerReady.bind(_assertThisInitialized(_this)); | ||
_this.onPlayerStateChange = _this.onPlayerStateChange.bind(_assertThisInitialized(_this)); | ||
_this.refContainer = _this.refContainer.bind(_assertThisInitialized(_this)); | ||
return _this; | ||
} | ||
return () => { | ||
if (handler) { | ||
player === null || player === void 0 ? void 0 : player.removeEventListener(event, handler); | ||
} | ||
}; | ||
}, [player, event, handler]); | ||
} | ||
/** @param {import('../index').YouTubeProps} props */ | ||
var _proto = YouTube.prototype; | ||
_proto.componentDidMount = function componentDidMount() { | ||
this.createPlayer(); | ||
}; | ||
function YouTube(_ref) { | ||
let { | ||
video, | ||
id, | ||
className, | ||
style, | ||
startSeconds, | ||
endSeconds, | ||
width, | ||
height, | ||
lang, | ||
paused, | ||
muted, | ||
volume, | ||
suggestedQuality, | ||
playbackRate, | ||
autoplay = false, | ||
showCaptions = false, | ||
controls = true, | ||
disableKeyboard = false, | ||
allowFullscreen = true, | ||
annotations = true, | ||
modestBranding = false, | ||
playsInline = false, | ||
showRelatedVideos = true, | ||
showInfo = true, | ||
onReady, | ||
onError, | ||
onStateChange, | ||
onPlaybackQualityChange, | ||
onPlaybackRateChange, | ||
onCued = () => {}, | ||
onBuffering = () => {}, | ||
onPlaying = () => {}, | ||
onPause = () => {}, | ||
onEnd = () => {} | ||
} = _ref; | ||
_proto.componentDidUpdate = function componentDidUpdate(prevProps) { | ||
var _this2 = this; | ||
/** @type {React.RefObject<HTMLDivElement | null>} */ | ||
const container = useRef(null); | ||
/** @type {React.MutableRefObject<() => YT.Player>} */ | ||
// eslint-disable-next-line react/destructuring-assignment | ||
var changes = Object.keys(this.props).filter(function (name) { | ||
return _this2.props[name] !== prevProps[name]; | ||
}); | ||
this.updateProps(changes); | ||
}; | ||
const createPlayer = useRef(null); | ||
const firstRender = useRef(false); | ||
const [player, setPlayer] = useState( | ||
/** @type {YT.Player | null} */ | ||
null); | ||
/** @type {YT.PlayerVars} */ | ||
_proto.componentWillUnmount = function componentWillUnmount() { | ||
if (this.playerInstance) { | ||
this.playerInstance.destroy(); | ||
} | ||
}; | ||
const playerVars = { | ||
autoplay: autoplay ? 1 : 0, | ||
cc_load_policy: showCaptions ? 1 : 0, | ||
controls: controls ? 1 : 0, | ||
disablekb: disableKeyboard ? 1 : 0, | ||
fs: allowFullscreen ? 1 : 0, | ||
hl: lang, | ||
iv_load_policy: annotations ? 1 : 3, | ||
start: startSeconds, | ||
end: endSeconds, | ||
modestbranding: modestBranding ? 1 : 0, | ||
playsinline: playsInline ? 1 : 0, | ||
rel: showRelatedVideos ? 1 : 0, | ||
showinfo: showInfo ? 1 : 0 | ||
}; // Stick the player initialisation in a ref so it has the most recent props values | ||
// when it gets instantiated. | ||
_proto.onPlayerReady = function onPlayerReady(event) { | ||
var _this$props = this.props, | ||
volume = _this$props.volume, | ||
muted = _this$props.muted, | ||
suggestedQuality = _this$props.suggestedQuality, | ||
playbackRate = _this$props.playbackRate; | ||
if (typeof volume !== 'undefined') { | ||
event.target.setVolume(volume * 100); | ||
} | ||
if (typeof muted !== 'undefined') { | ||
if (muted) { | ||
event.target.mute(); | ||
} else { | ||
event.target.unMute(); | ||
if (!player) { | ||
// eslint-disable-next-line no-undef | ||
createPlayer.current = () => new YT.Player(container.current, { | ||
videoId: video, | ||
width, | ||
height, | ||
playerVars, | ||
events: { | ||
onReady: event => { | ||
firstRender.current = true; | ||
setPlayer(event.target); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
if (typeof suggestedQuality !== 'undefined') { | ||
event.target.setPlaybackQuality(suggestedQuality); | ||
} | ||
const handlePlayerStateChange = useCallback(event => { | ||
const State = YT.PlayerState; // eslint-disable-line no-undef | ||
if (typeof playbackRate !== 'undefined') { | ||
event.target.setPlaybackRate(playbackRate); | ||
} | ||
this.resolvePlayer(event.target); | ||
}; | ||
_proto.onPlayerStateChange = function onPlayerStateChange(event) { | ||
var _this$props2 = this.props, | ||
onCued = _this$props2.onCued, | ||
onBuffering = _this$props2.onBuffering, | ||
onPause = _this$props2.onPause, | ||
onPlaying = _this$props2.onPlaying, | ||
onEnd = _this$props2.onEnd; | ||
var State = YT.PlayerState; // eslint-disable-line no-undef | ||
switch (event.data) { | ||
@@ -161,179 +190,98 @@ case State.CUED: | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
; | ||
}, [onCued, onBuffering, onPause, onPlaying, onEnd]); // The effect that manages the player's lifetime. | ||
_proto.getPlayerParameters = function getPlayerParameters() { | ||
/* eslint-disable react/destructuring-assignment */ | ||
return { | ||
autoplay: this.props.autoplay, | ||
cc_load_policy: this.props.showCaptions ? 1 : 0, | ||
controls: this.props.controls ? 1 : 0, | ||
disablekb: this.props.disableKeyboard ? 1 : 0, | ||
fs: this.props.allowFullscreen ? 1 : 0, | ||
hl: this.props.lang, | ||
iv_load_policy: this.props.annotations ? 1 : 3, | ||
start: this.props.startSeconds, | ||
end: this.props.endSeconds, | ||
modestbranding: this.props.modestBranding ? 1 : 0, | ||
playsinline: this.props.playsInline ? 1 : 0, | ||
rel: this.props.showRelatedVideos ? 1 : 0, | ||
showinfo: this.props.showInfo ? 1 : 0 | ||
}; | ||
/* eslint-enable react/destructuring-assignment */ | ||
} | ||
/** | ||
* @private | ||
*/ | ||
; | ||
_proto.getInitialOptions = function getInitialOptions() { | ||
/* eslint-disable react/destructuring-assignment */ | ||
return { | ||
videoId: this.props.video, | ||
width: this.props.width, | ||
height: this.props.height, | ||
playerVars: this.getPlayerParameters(), | ||
events: { | ||
onReady: this.onPlayerReady, | ||
onStateChange: this.onPlayerStateChange | ||
useEffect(() => { | ||
let instance = null; | ||
let cancelled = false; | ||
getSdk().then(() => { | ||
if (!cancelled) { | ||
instance = createPlayer.current(); | ||
} | ||
}; | ||
/* eslint-enable react/destructuring-assignment */ | ||
} | ||
/** | ||
* @private | ||
*/ | ||
; | ||
_proto.updateProps = function updateProps(propNames) { | ||
var _this3 = this; | ||
this.player.then(function (player) { | ||
propNames.forEach(function (name) { | ||
// eslint-disable-next-line react/destructuring-assignment | ||
var value = _this3.props[name]; | ||
switch (name) { | ||
case 'muted': | ||
if (value) { | ||
player.mute(); | ||
} else { | ||
player.unMute(); | ||
} | ||
break; | ||
case 'suggestedQuality': | ||
player.setPlaybackQuality(value); | ||
break; | ||
case 'volume': | ||
player.setVolume(value * 100); | ||
break; | ||
case 'paused': | ||
if (value && player.getPlayerState() !== 2) { | ||
player.pauseVideo(); | ||
} else if (!value && player.getPlayerState() === 2) { | ||
player.playVideo(); | ||
} | ||
break; | ||
case 'id': | ||
case 'className': | ||
case 'width': | ||
case 'height': | ||
player.getIframe()[name] = value; // eslint-disable-line no-param-reassign | ||
break; | ||
case 'video': | ||
if (!value) { | ||
player.stopVideo(); | ||
} else { | ||
var _this3$props = _this3.props, | ||
startSeconds = _this3$props.startSeconds, | ||
endSeconds = _this3$props.endSeconds, | ||
autoplay = _this3$props.autoplay; | ||
var opts = { | ||
videoId: value, | ||
startSeconds: startSeconds || 0, | ||
endSeconds: endSeconds | ||
}; | ||
if (autoplay) { | ||
player.loadVideoById(opts); | ||
} else { | ||
player.cueVideoById(opts); | ||
} | ||
} | ||
break; | ||
} | ||
}); | ||
}); | ||
} | ||
/** | ||
* @private | ||
*/ | ||
; | ||
return () => { | ||
var _instance; | ||
_proto.createPlayer = function createPlayer() { | ||
var _this4 = this; | ||
cancelled = true; | ||
(_instance = instance) === null || _instance === void 0 ? void 0 : _instance.destroy(); | ||
}; | ||
}, []); | ||
useEventHandler(player, 'onStateChange', handlePlayerStateChange); | ||
useEventHandler(player, 'onReady', onReady); | ||
useEventHandler(player, 'onStateChange', onStateChange); | ||
useEventHandler(player, 'onPlaybackQualityChange', onPlaybackQualityChange); | ||
useEventHandler(player, 'onPlaybackRateChange', onPlaybackRateChange); | ||
useEventHandler(player, 'onError', onError); | ||
useEffect(() => { | ||
if (player) { | ||
player.getIframe().width = width; | ||
} | ||
}, [player, width]); | ||
useEffect(() => { | ||
if (player) { | ||
player.getIframe().height = height; | ||
} | ||
}, [player, height]); | ||
useEffect(() => { | ||
if (muted) { | ||
player === null || player === void 0 ? void 0 : player.mute(); | ||
} else { | ||
player === null || player === void 0 ? void 0 : player.unMute(); | ||
} | ||
}, [player, muted]); | ||
useEffect(() => { | ||
player === null || player === void 0 ? void 0 : player.setPlaybackQuality(suggestedQuality); | ||
}, [player, suggestedQuality]); | ||
useEffect(() => { | ||
player === null || player === void 0 ? void 0 : player.setPlaybackRate(playbackRate); | ||
}, [player, playbackRate]); | ||
useEffect(() => { | ||
player === null || player === void 0 ? void 0 : player.setVolume(volume * 100); | ||
}, [player, volume]); | ||
useEffect(() => { | ||
if (!player) { | ||
return; | ||
} | ||
var volume = this.props.volume; | ||
this.player = getSdk().then(function (YT) { | ||
return new Promise(function (resolve) { | ||
_this4.resolvePlayer = resolve; | ||
var player = new YT.Player(_this4.container, _this4.getInitialOptions()); // Store the instance directly so we can destroy it sync in | ||
// `componentWillUnmount`. | ||
if (paused && player.getPlayerState() !== 2) { | ||
player.pauseVideo(); | ||
} else if (!paused && player.getPlayerState() === 2) { | ||
player.playVideo(); | ||
} | ||
}, [player, paused]); | ||
useEffect(() => { | ||
if (!player) { | ||
return; | ||
} // Avoid calling a load() function when the player has just initialised, | ||
// since it will already be up to date at that stage. | ||
_this4.playerInstance = player; | ||
eventNames.forEach(function (name) { | ||
player.addEventListener(name, function (event) { | ||
// eslint-disable-next-line react/destructuring-assignment | ||
var handler = _this4.props[name]; | ||
if (handler) { | ||
handler(event); | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
if (typeof volume === 'number') { | ||
this.updateProps(['volume']); | ||
if (firstRender.current) { | ||
firstRender.current = false; | ||
return; | ||
} | ||
} | ||
/** | ||
* @private | ||
*/ | ||
; | ||
_proto.refContainer = function refContainer(container) { | ||
this.container = container; | ||
}; | ||
if (!video) { | ||
player.stopVideo(); | ||
} else { | ||
const opts = { | ||
videoId: video, | ||
startSeconds: startSeconds || 0, | ||
endSeconds | ||
}; | ||
_proto.render = function render() { | ||
var _this$props3 = this.props, | ||
id = _this$props3.id, | ||
className = _this$props3.className, | ||
style = _this$props3.style; | ||
return /*#__PURE__*/React.createElement("div", { | ||
id: id, | ||
className: className, | ||
style: style, | ||
ref: this.refContainer | ||
}); | ||
}; | ||
if (autoplay) { | ||
player.loadVideoById(opts); | ||
} else { | ||
player.cueVideoById(opts); | ||
} | ||
} | ||
}, [player, video]); | ||
return /*#__PURE__*/React__default["default"].createElement("div", { | ||
id: id, | ||
className: className, | ||
style: style, | ||
ref: container | ||
}); | ||
} | ||
return YouTube; | ||
}(React.Component); | ||
if (process.env.NODE_ENV !== 'production') { | ||
@@ -344,3 +292,3 @@ YouTube.propTypes = { | ||
*/ | ||
video: PropTypes__default['default'].string, | ||
video: PropTypes__default["default"].string, | ||
@@ -350,3 +298,3 @@ /** | ||
*/ | ||
id: PropTypes__default['default'].string, | ||
id: PropTypes__default["default"].string, | ||
@@ -356,3 +304,3 @@ /** | ||
*/ | ||
className: PropTypes__default['default'].string, | ||
className: PropTypes__default["default"].string, | ||
@@ -362,3 +310,3 @@ /** | ||
*/ | ||
style: PropTypes__default['default'].object, | ||
style: PropTypes__default["default"].object, | ||
// eslint-disable-line react/forbid-prop-types | ||
@@ -369,3 +317,3 @@ | ||
*/ | ||
width: PropTypes__default['default'].oneOfType([PropTypes__default['default'].number, PropTypes__default['default'].string]), | ||
width: PropTypes__default["default"].oneOfType([PropTypes__default["default"].number, PropTypes__default["default"].string]), | ||
@@ -375,3 +323,3 @@ /** | ||
*/ | ||
height: PropTypes__default['default'].oneOfType([PropTypes__default['default'].number, PropTypes__default['default'].string]), | ||
height: PropTypes__default["default"].oneOfType([PropTypes__default["default"].number, PropTypes__default["default"].string]), | ||
@@ -381,3 +329,3 @@ /** | ||
*/ | ||
paused: PropTypes__default['default'].bool, | ||
paused: PropTypes__default["default"].bool, | ||
// eslint-disable-line react/no-unused-prop-types | ||
@@ -391,3 +339,3 @@ // Player parameters | ||
*/ | ||
autoplay: PropTypes__default['default'].bool, | ||
autoplay: PropTypes__default["default"].bool, | ||
@@ -399,3 +347,3 @@ /** | ||
*/ | ||
showCaptions: PropTypes__default['default'].bool, | ||
showCaptions: PropTypes__default["default"].bool, | ||
@@ -407,3 +355,3 @@ /** | ||
*/ | ||
controls: PropTypes__default['default'].bool, | ||
controls: PropTypes__default["default"].bool, | ||
@@ -415,3 +363,3 @@ /** | ||
*/ | ||
disableKeyboard: PropTypes__default['default'].bool, | ||
disableKeyboard: PropTypes__default["default"].bool, | ||
@@ -423,3 +371,3 @@ /** | ||
*/ | ||
allowFullscreen: PropTypes__default['default'].bool, | ||
allowFullscreen: PropTypes__default["default"].bool, | ||
@@ -432,3 +380,3 @@ /** | ||
*/ | ||
lang: PropTypes__default['default'].string, | ||
lang: PropTypes__default["default"].string, | ||
@@ -440,3 +388,3 @@ /** | ||
*/ | ||
annotations: PropTypes__default['default'].bool, | ||
annotations: PropTypes__default["default"].bool, | ||
@@ -448,3 +396,3 @@ /** | ||
*/ | ||
startSeconds: PropTypes__default['default'].number, | ||
startSeconds: PropTypes__default["default"].number, | ||
@@ -456,3 +404,3 @@ /** | ||
*/ | ||
endSeconds: PropTypes__default['default'].number, | ||
endSeconds: PropTypes__default["default"].number, | ||
@@ -464,3 +412,3 @@ /** | ||
*/ | ||
modestBranding: PropTypes__default['default'].bool, | ||
modestBranding: PropTypes__default["default"].bool, | ||
@@ -472,3 +420,3 @@ /** | ||
*/ | ||
playsInline: PropTypes__default['default'].bool, | ||
playsInline: PropTypes__default["default"].bool, | ||
@@ -480,3 +428,3 @@ /** | ||
*/ | ||
showRelatedVideos: PropTypes__default['default'].bool, | ||
showRelatedVideos: PropTypes__default["default"].bool, | ||
@@ -491,3 +439,3 @@ /** | ||
*/ | ||
showInfo: PropTypes__default['default'].bool, | ||
showInfo: PropTypes__default["default"].bool, | ||
@@ -497,3 +445,3 @@ /** | ||
*/ | ||
volume: PropTypes__default['default'].number, | ||
volume: PropTypes__default["default"].number, | ||
@@ -503,3 +451,3 @@ /** | ||
*/ | ||
muted: PropTypes__default['default'].bool, | ||
muted: PropTypes__default["default"].bool, | ||
@@ -511,3 +459,3 @@ /** | ||
*/ | ||
suggestedQuality: PropTypes__default['default'].string, | ||
suggestedQuality: PropTypes__default["default"].string, | ||
@@ -519,11 +467,9 @@ /** | ||
*/ | ||
playbackRate: PropTypes__default['default'].number, | ||
playbackRate: PropTypes__default["default"].number, | ||
// Events | ||
/* eslint-disable react/no-unused-prop-types */ | ||
/** | ||
* Sent when the YouTube player API has loaded. | ||
*/ | ||
onReady: PropTypes__default['default'].func, | ||
onReady: PropTypes__default["default"].func, | ||
@@ -533,3 +479,3 @@ /** | ||
*/ | ||
onError: PropTypes__default['default'].func, | ||
onError: PropTypes__default["default"].func, | ||
@@ -539,3 +485,3 @@ /** | ||
*/ | ||
onCued: PropTypes__default['default'].func, | ||
onCued: PropTypes__default["default"].func, | ||
@@ -545,3 +491,3 @@ /** | ||
*/ | ||
onBuffering: PropTypes__default['default'].func, | ||
onBuffering: PropTypes__default["default"].func, | ||
@@ -551,3 +497,3 @@ /** | ||
*/ | ||
onPlaying: PropTypes__default['default'].func, | ||
onPlaying: PropTypes__default["default"].func, | ||
@@ -557,3 +503,3 @@ /** | ||
*/ | ||
onPause: PropTypes__default['default'].func, | ||
onPause: PropTypes__default["default"].func, | ||
@@ -563,8 +509,6 @@ /** | ||
*/ | ||
onEnd: PropTypes__default['default'].func, | ||
onStateChange: PropTypes__default['default'].func, | ||
onPlaybackRateChange: PropTypes__default['default'].func, | ||
onPlaybackQualityChange: PropTypes__default['default'].func | ||
/* eslint-enable react/no-unused-prop-types */ | ||
onEnd: PropTypes__default["default"].func, | ||
onStateChange: PropTypes__default["default"].func, | ||
onPlaybackRateChange: PropTypes__default["default"].func, | ||
onPlaybackQualityChange: PropTypes__default["default"].func | ||
}; | ||
@@ -584,9 +528,10 @@ } | ||
showInfo: true, | ||
onCued: function onCued() {}, | ||
onBuffering: function onBuffering() {}, | ||
onPlaying: function onPlaying() {}, | ||
onPause: function onPause() {}, | ||
onEnd: function onEnd() {} | ||
onCued: () => {}, | ||
onBuffering: () => {}, | ||
onPlaying: () => {}, | ||
onPause: () => {}, | ||
onEnd: () => {} | ||
}; | ||
exports.default = YouTube; | ||
exports["default"] = YouTube; | ||
//# sourceMappingURL=react-youtube.js.map |
{ | ||
"name": "@u-wave/react-youtube", | ||
"description": "YouTube player component for React.", | ||
"version": "0.7.2", | ||
"version": "1.0.0-alpha.0", | ||
"author": "Renée Kooi <renee@kooi.me>", | ||
@@ -10,5 +10,4 @@ "bugs": { | ||
"dependencies": { | ||
"@types/react": "^16.9.17", | ||
"@types/youtube": "0.0.39", | ||
"load-script2": "^1.0.1", | ||
"@types/react": "^17.0.0", | ||
"@types/youtube": "0.0.46", | ||
"prop-types": "^15.7.2" | ||
@@ -23,5 +22,4 @@ }, | ||
"@rollup/plugin-babel": "^5.0.4", | ||
"cross-env": "^7.0.0", | ||
"eslint": "^7.4.0", | ||
"eslint-config-airbnb": "^18.2.0", | ||
"eslint": "^8.2.0", | ||
"eslint-config-airbnb": "^19.0.0", | ||
"eslint-plugin-import": "^2.22.0", | ||
@@ -34,3 +32,3 @@ "eslint-plugin-jsx-a11y": "^6.3.1", | ||
"min-react-env": "^1.0.1", | ||
"mocha": "^8.0.1", | ||
"mocha": "^9.0.0", | ||
"prop-types-table": "^1.0.0", | ||
@@ -42,3 +40,3 @@ "proxyquire": "^2.1.3", | ||
"rollup": "^2.0.2", | ||
"tsd": "^0.13.1" | ||
"tsd": "^0.19.0" | ||
}, | ||
@@ -54,6 +52,13 @@ "homepage": "https://github.com/u-wave/react-youtube#readme", | ||
"license": "MIT", | ||
"main": "dist/react-youtube.js", | ||
"module": "dist/react-youtube.es.js", | ||
"main": "./dist/react-youtube.js", | ||
"module": "./dist/react-youtube.mjs", | ||
"exports": { | ||
".": { | ||
"require": "./dist/react-youtube.js", | ||
"import": "./dist/react-youtube.mjs" | ||
} | ||
}, | ||
"types": "index.d.ts", | ||
"peerDependencies": { | ||
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" | ||
"react": "^17.0.0 || ^18.0.0-0" | ||
}, | ||
@@ -67,10 +72,17 @@ "repository": { | ||
"docs": "prop-types-table src/index.js | md-insert README.md --header Props -i", | ||
"example": "npm install && cd example && npm install", | ||
"example": "npm run -w example build && npm run -w example start", | ||
"prepare": "npm run build", | ||
"test": "npm run test:types && npm run test:mocha && npm run test:lint", | ||
"test:lint": "eslint --cache --fix .", | ||
"test:mocha": "cross-env NODE_ENV=test mocha --require @babel/register test/*.js", | ||
"test:types": "tsd" | ||
"browserslist": "npx browserslist --mobile-to-desktop '> 0.5%, last 2 versions, Firefox ESR, not dead, not IE 11' > .browserslistrc", | ||
"test": "npm run tsd && npm run tests-only && npm run lint", | ||
"lint": "eslint --cache --fix .", | ||
"tests-only": "mocha --require @babel/register test/*.js", | ||
"tsd": "tsd" | ||
}, | ||
"workspaces": { | ||
"packages": [ | ||
".", | ||
"./example" | ||
] | ||
}, | ||
"sideEffects": false | ||
} |
@@ -8,4 +8,13 @@ import babel from '@rollup/plugin-babel'; | ||
output: [ | ||
{ format: 'cjs', file: meta.main, exports: 'named' }, | ||
{ format: 'es', file: meta.module }, | ||
{ | ||
format: 'cjs', | ||
file: meta.exports['.'].require, | ||
exports: 'named', | ||
sourcemap: true, | ||
}, | ||
{ | ||
format: 'esm', | ||
file: meta.exports['.'].import, | ||
sourcemap: true, | ||
}, | ||
], | ||
@@ -12,0 +21,0 @@ |
392
src/index.js
@@ -1,69 +0,112 @@ | ||
import * as React from 'react'; | ||
// @ts-check | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import eventNames from './eventNames'; | ||
import loadSdk from './loadSdk'; | ||
class YouTube extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
const { | ||
useCallback, | ||
useEffect, | ||
useRef, | ||
useState, | ||
} = React; | ||
this.onPlayerReady = this.onPlayerReady.bind(this); | ||
this.onPlayerStateChange = this.onPlayerStateChange.bind(this); | ||
this.refContainer = this.refContainer.bind(this); | ||
} | ||
componentDidMount() { | ||
this.createPlayer(); | ||
} | ||
componentDidUpdate(prevProps) { | ||
// eslint-disable-next-line react/destructuring-assignment | ||
const changes = Object.keys(this.props).filter((name) => this.props[name] !== prevProps[name]); | ||
this.updateProps(changes); | ||
} | ||
componentWillUnmount() { | ||
if (this.playerInstance) { | ||
this.playerInstance.destroy(); | ||
/** | ||
* @template {keyof YT.Events} K | ||
* @param {YT.Player} player | ||
* @param {K} event | ||
* @param {YT.Events[K]} handler | ||
*/ | ||
function useEventHandler(player, event, handler) { | ||
useEffect(() => { | ||
if (handler) { | ||
player?.addEventListener(event, handler); | ||
} | ||
} | ||
return () => { | ||
if (handler) { | ||
player?.removeEventListener(event, handler); | ||
} | ||
}; | ||
}, [player, event, handler]); | ||
} | ||
onPlayerReady(event) { | ||
const { | ||
volume, | ||
muted, | ||
suggestedQuality, | ||
playbackRate, | ||
} = this.props; | ||
/** @param {import('../index').YouTubeProps} props */ | ||
function YouTube({ | ||
video, | ||
id, | ||
className, | ||
style, | ||
startSeconds, | ||
endSeconds, | ||
width, | ||
height, | ||
lang, | ||
paused, | ||
muted, | ||
volume, | ||
suggestedQuality, | ||
playbackRate, | ||
autoplay = false, | ||
showCaptions = false, | ||
controls = true, | ||
disableKeyboard = false, | ||
allowFullscreen = true, | ||
annotations = true, | ||
modestBranding = false, | ||
playsInline = false, | ||
showRelatedVideos = true, | ||
showInfo = true, | ||
onReady, | ||
onError, | ||
onStateChange, | ||
onPlaybackQualityChange, | ||
onPlaybackRateChange, | ||
onCued = () => {}, | ||
onBuffering = () => {}, | ||
onPlaying = () => {}, | ||
onPause = () => {}, | ||
onEnd = () => {}, | ||
}) { | ||
/** @type {React.RefObject<HTMLDivElement | null>} */ | ||
const container = useRef(null); | ||
/** @type {React.MutableRefObject<() => YT.Player>} */ | ||
const createPlayer = useRef(null); | ||
const firstRender = useRef(false); | ||
const [player, setPlayer] = useState(/** @type {YT.Player | null} */ (null)); | ||
if (typeof volume !== 'undefined') { | ||
event.target.setVolume(volume * 100); | ||
} | ||
if (typeof muted !== 'undefined') { | ||
if (muted) { | ||
event.target.mute(); | ||
} else { | ||
event.target.unMute(); | ||
} | ||
} | ||
if (typeof suggestedQuality !== 'undefined') { | ||
event.target.setPlaybackQuality(suggestedQuality); | ||
} | ||
if (typeof playbackRate !== 'undefined') { | ||
event.target.setPlaybackRate(playbackRate); | ||
} | ||
/** @type {YT.PlayerVars} */ | ||
const playerVars = { | ||
autoplay: autoplay ? 1 : 0, | ||
cc_load_policy: showCaptions ? 1 : 0, | ||
controls: controls ? 1 : 0, | ||
disablekb: disableKeyboard ? 1 : 0, | ||
fs: allowFullscreen ? 1 : 0, | ||
hl: lang, | ||
iv_load_policy: annotations ? 1 : 3, | ||
start: startSeconds, | ||
end: endSeconds, | ||
modestbranding: modestBranding ? 1 : 0, | ||
playsinline: playsInline ? 1 : 0, | ||
rel: showRelatedVideos ? 1 : 0, | ||
showinfo: showInfo ? 1 : 0, | ||
}; | ||
this.resolvePlayer(event.target); | ||
// Stick the player initialisation in a ref so it has the most recent props values | ||
// when it gets instantiated. | ||
if (!player) { | ||
// eslint-disable-next-line no-undef | ||
createPlayer.current = () => new YT.Player(container.current, { | ||
videoId: video, | ||
width, | ||
height, | ||
playerVars, | ||
events: { | ||
onReady: (event) => { | ||
firstRender.current = true; | ||
setPlayer(event.target); | ||
}, | ||
}, | ||
}); | ||
} | ||
onPlayerStateChange(event) { | ||
const { | ||
onCued, | ||
onBuffering, | ||
onPause, | ||
onPlaying, | ||
onEnd, | ||
} = this.props; | ||
const handlePlayerStateChange = useCallback((event) => { | ||
const State = YT.PlayerState; // eslint-disable-line no-undef | ||
@@ -89,153 +132,107 @@ switch (event.data) { | ||
} | ||
} | ||
}, [onCued, onBuffering, onPause, onPlaying, onEnd]); | ||
/** | ||
* @private | ||
*/ | ||
getPlayerParameters() { | ||
/* eslint-disable react/destructuring-assignment */ | ||
return { | ||
autoplay: this.props.autoplay, | ||
cc_load_policy: this.props.showCaptions ? 1 : 0, | ||
controls: this.props.controls ? 1 : 0, | ||
disablekb: this.props.disableKeyboard ? 1 : 0, | ||
fs: this.props.allowFullscreen ? 1 : 0, | ||
hl: this.props.lang, | ||
iv_load_policy: this.props.annotations ? 1 : 3, | ||
start: this.props.startSeconds, | ||
end: this.props.endSeconds, | ||
modestbranding: this.props.modestBranding ? 1 : 0, | ||
playsinline: this.props.playsInline ? 1 : 0, | ||
rel: this.props.showRelatedVideos ? 1 : 0, | ||
showinfo: this.props.showInfo ? 1 : 0, | ||
}; | ||
/* eslint-enable react/destructuring-assignment */ | ||
} | ||
// The effect that manages the player's lifetime. | ||
useEffect(() => { | ||
let instance = null; | ||
let cancelled = false; | ||
/** | ||
* @private | ||
*/ | ||
getInitialOptions() { | ||
/* eslint-disable react/destructuring-assignment */ | ||
return { | ||
videoId: this.props.video, | ||
width: this.props.width, | ||
height: this.props.height, | ||
playerVars: this.getPlayerParameters(), | ||
events: { | ||
onReady: this.onPlayerReady, | ||
onStateChange: this.onPlayerStateChange, | ||
}, | ||
loadSdk().then(() => { | ||
if (!cancelled) { | ||
instance = createPlayer.current(); | ||
} | ||
}); | ||
return () => { | ||
cancelled = true; | ||
instance?.destroy(); | ||
}; | ||
/* eslint-enable react/destructuring-assignment */ | ||
} | ||
}, []); | ||
/** | ||
* @private | ||
*/ | ||
updateProps(propNames) { | ||
this.player.then((player) => { | ||
propNames.forEach((name) => { | ||
// eslint-disable-next-line react/destructuring-assignment | ||
const value = this.props[name]; | ||
switch (name) { | ||
case 'muted': | ||
if (value) { | ||
player.mute(); | ||
} else { | ||
player.unMute(); | ||
} | ||
break; | ||
case 'suggestedQuality': | ||
player.setPlaybackQuality(value); | ||
break; | ||
case 'volume': | ||
player.setVolume(value * 100); | ||
break; | ||
case 'paused': | ||
if (value && player.getPlayerState() !== 2) { | ||
player.pauseVideo(); | ||
} else if (!value && player.getPlayerState() === 2) { | ||
player.playVideo(); | ||
} | ||
break; | ||
case 'id': | ||
case 'className': | ||
case 'width': | ||
case 'height': | ||
player.getIframe()[name] = value; // eslint-disable-line no-param-reassign | ||
break; | ||
case 'video': | ||
if (!value) { | ||
player.stopVideo(); | ||
} else { | ||
const { startSeconds, endSeconds, autoplay } = this.props; | ||
const opts = { | ||
videoId: value, | ||
startSeconds: startSeconds || 0, | ||
endSeconds, | ||
}; | ||
if (autoplay) { | ||
player.loadVideoById(opts); | ||
} else { | ||
player.cueVideoById(opts); | ||
} | ||
} | ||
break; | ||
default: | ||
// Nothing | ||
} | ||
}); | ||
}); | ||
} | ||
useEventHandler(player, 'onStateChange', handlePlayerStateChange); | ||
useEventHandler(player, 'onReady', onReady); | ||
useEventHandler(player, 'onStateChange', onStateChange); | ||
useEventHandler(player, 'onPlaybackQualityChange', onPlaybackQualityChange); | ||
useEventHandler(player, 'onPlaybackRateChange', onPlaybackRateChange); | ||
useEventHandler(player, 'onError', onError); | ||
/** | ||
* @private | ||
*/ | ||
createPlayer() { | ||
const { volume } = this.props; | ||
useEffect(() => { | ||
if (player) { | ||
player.getIframe().width = width; | ||
} | ||
}, [player, width]); | ||
this.player = loadSdk().then((YT) => new Promise((resolve) => { | ||
this.resolvePlayer = resolve; | ||
useEffect(() => { | ||
if (player) { | ||
player.getIframe().height = height; | ||
} | ||
}, [player, height]); | ||
const player = new YT.Player(this.container, this.getInitialOptions()); | ||
// Store the instance directly so we can destroy it sync in | ||
// `componentWillUnmount`. | ||
this.playerInstance = player; | ||
useEffect(() => { | ||
if (muted) { | ||
player?.mute(); | ||
} else { | ||
player?.unMute(); | ||
} | ||
}, [player, muted]); | ||
eventNames.forEach((name) => { | ||
player.addEventListener(name, (event) => { | ||
// eslint-disable-next-line react/destructuring-assignment | ||
const handler = this.props[name]; | ||
if (handler) { | ||
handler(event); | ||
} | ||
}); | ||
}); | ||
})); | ||
useEffect(() => { | ||
player?.setPlaybackQuality(suggestedQuality); | ||
}, [player, suggestedQuality]); | ||
if (typeof volume === 'number') { | ||
this.updateProps(['volume']); | ||
useEffect(() => { | ||
player?.setPlaybackRate(playbackRate); | ||
}, [player, playbackRate]); | ||
useEffect(() => { | ||
player?.setVolume(volume * 100); | ||
}, [player, volume]); | ||
useEffect(() => { | ||
if (!player) { | ||
return; | ||
} | ||
} | ||
if (paused && player.getPlayerState() !== 2) { | ||
player.pauseVideo(); | ||
} else if (!paused && player.getPlayerState() === 2) { | ||
player.playVideo(); | ||
} | ||
}, [player, paused]); | ||
/** | ||
* @private | ||
*/ | ||
refContainer(container) { | ||
this.container = container; | ||
} | ||
useEffect(() => { | ||
if (!player) { | ||
return; | ||
} | ||
render() { | ||
const { id, className, style } = this.props; | ||
// Avoid calling a load() function when the player has just initialised, | ||
// since it will already be up to date at that stage. | ||
if (firstRender.current) { | ||
firstRender.current = false; | ||
return; | ||
} | ||
return ( | ||
<div | ||
id={id} | ||
className={className} | ||
style={style} | ||
ref={this.refContainer} | ||
/> | ||
); | ||
} | ||
if (!video) { | ||
player.stopVideo(); | ||
} else { | ||
const opts = { | ||
videoId: video, | ||
startSeconds: startSeconds || 0, | ||
endSeconds, | ||
}; | ||
if (autoplay) { | ||
player.loadVideoById(opts); | ||
} else { | ||
player.cueVideoById(opts); | ||
} | ||
} | ||
}, [player, video]); | ||
return ( | ||
<div | ||
id={id} | ||
className={className} | ||
style={style} | ||
ref={container} | ||
/> | ||
); | ||
} | ||
@@ -390,3 +387,2 @@ | ||
// Events | ||
/* eslint-disable react/no-unused-prop-types */ | ||
@@ -424,4 +420,2 @@ /** | ||
onPlaybackQualityChange: PropTypes.func, | ||
/* eslint-enable react/no-unused-prop-types */ | ||
}; | ||
@@ -428,0 +422,0 @@ } |
@@ -1,3 +0,2 @@ | ||
/* global window */ | ||
import loadScript from 'load-script2'; | ||
/* global window, document */ | ||
@@ -8,17 +7,21 @@ function loadSdk() { | ||
// A YouTube SDK is already loaded, so reuse that | ||
window.YT.ready(() => { | ||
resolve(window.YT); | ||
}); | ||
window.YT.ready(resolve); | ||
return; | ||
} | ||
loadScript('https://www.youtube.com/iframe_api', (err) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
window.YT.ready(() => { | ||
resolve(window.YT); | ||
}); | ||
} | ||
}); | ||
const script = document.createElement('script'); | ||
script.async = true; | ||
script.src = 'https://www.youtube.com/iframe_api'; | ||
script.onload = () => { | ||
script.onerror = null; | ||
script.onload = null; | ||
window.YT.ready(resolve); | ||
}; | ||
script.onerror = () => { | ||
script.onerror = null; | ||
script.onload = null; | ||
reject(new Error('Could not load YouTube SDK')); | ||
}; | ||
document.head.appendChild(script); | ||
}); | ||
@@ -25,0 +28,0 @@ } |
@@ -28,4 +28,5 @@ import { createSpy } from 'expect'; | ||
addEventListener: createSpy().andCall((eventName, fn) => { | ||
if (eventName === 'ready') fn(); | ||
if (eventName === 'onReady') fn({ target: playerMock }); | ||
}), | ||
removeEventListener: createSpy(), | ||
mute: createSpy(), | ||
@@ -69,3 +70,6 @@ unMute: createSpy(), | ||
'./loadSdk': { | ||
default: () => Promise.resolve(sdkMock), | ||
default() { | ||
global.YT = sdkMock; | ||
return Promise.resolve(sdkMock); | ||
}, | ||
}, | ||
@@ -72,0 +76,0 @@ }).default; |
@@ -8,2 +8,3 @@ /** | ||
import ReactDOM from 'react-dom'; | ||
import { act } from 'react-dom/test-utils'; | ||
import env from 'min-react-env'; | ||
@@ -14,6 +15,10 @@ import createYouTube from './createYouTube'; | ||
const render = (initialProps) => { | ||
async function render(initialProps) { | ||
const { YouTube, sdkMock, playerMock } = createYouTube(); | ||
let component; | ||
let resolveReady; | ||
const readyPromise = new Promise((resolve) => { | ||
resolveReady = resolve; | ||
}); | ||
// Emulate changes to component.props using a container component's state | ||
@@ -30,6 +35,11 @@ class Container extends React.Component { | ||
const onReady = (event) => { | ||
resolveReady(); | ||
props.onReady?.(event); | ||
}; | ||
return ( | ||
<YouTube | ||
ref={(youtube) => { component = youtube; }} | ||
{...props} | ||
onReady={onReady} | ||
/> | ||
@@ -42,11 +52,16 @@ ); | ||
const container = new Promise((resolve) => { | ||
ReactDOM.render(<Container {...initialProps} ref={resolve} />, div); | ||
if (ReactDOM.version.startsWith('18')) { | ||
ReactDOM.createRoot(div).render(<Container {...initialProps} ref={resolve} />); | ||
} else { | ||
ReactDOM.render(<Container {...initialProps} ref={resolve} />, div); | ||
} | ||
}); | ||
await readyPromise; | ||
function rerender(newProps) { | ||
return container.then((wrapper) => new Promise((resolve) => { | ||
wrapper.setState({ props: newProps }, () => { | ||
Promise.resolve().then(resolve); | ||
}); | ||
})); | ||
async function rerender(newProps) { | ||
const wrapper = await container; | ||
act(() => { | ||
wrapper.setState({ props: newProps }); | ||
}); | ||
} | ||
@@ -58,11 +73,10 @@ | ||
return component.player.then(() => ({ | ||
return { | ||
sdkMock, | ||
playerMock, | ||
component, | ||
rerender, | ||
unmount, | ||
})); | ||
}; | ||
}; | ||
} | ||
export default render; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
128530
4
23
27
2323
+ Added@types/react@17.0.83(transitive)
+ Added@types/youtube@0.0.46(transitive)
+ Addedreact@18.3.1(transitive)
- Removedload-script2@^1.0.1
- Removed@types/react@16.14.62(transitive)
- Removed@types/youtube@0.0.39(transitive)
- Removedload-script2@1.0.1(transitive)
- Removedreact@17.0.2(transitive)
Updated@types/react@^17.0.0
Updated@types/youtube@0.0.46