diff --git a/src/app/routerConfig.js b/src/app/routerConfig.js index 095a290d4..562ae002b 100644 --- a/src/app/routerConfig.js +++ b/src/app/routerConfig.js @@ -1,4 +1,4 @@ -import { Calendar, Discover, Addons, Settings, Board, Player } from 'stremio-routes'; +import { Calendar, Discover, Addons, Settings, Board, Player, Detail } from 'stremio-routes'; const config = { views: [ @@ -27,6 +27,10 @@ const config = { { path: '/settings', component: Settings + }, + { + path: '/detail', + component: Detail } ] }, diff --git a/src/common/Popup/Popup.js b/src/common/Popup/Popup.js index 372d4f3b0..6ad80d7bb 100644 --- a/src/common/Popup/Popup.js +++ b/src/common/Popup/Popup.js @@ -15,13 +15,15 @@ class Popup extends Component { this.labelBorderRightRef = React.createRef(); this.labelBorderBottomRef = React.createRef(); this.labelBorderLeftRef = React.createRef(); - this.menuRef = React.createRef(); + this.menuContainerRef = React.createRef(); this.menuScrollRef = React.createRef(); + this.menuChildrenRef = React.createRef(); this.menuBorderTopRef = React.createRef(); this.menuBorderRightRef = React.createRef(); this.menuBorderBottomRef = React.createRef(); this.menuBorderLeftRef = React.createRef(); this.hiddenBorderRef = React.createRef(); + this.popupMutationObserver = this.createPopupMutationObserver(); this.state = { open: false @@ -38,6 +40,7 @@ class Popup extends Component { window.removeEventListener('blur', this.close); window.removeEventListener('resize', this.close); window.removeEventListener('keyup', this.onKeyUp); + this.popupMutationObserver.disconnect(); } shouldComponentUpdate(nextProps, nextState) { @@ -48,19 +51,65 @@ class Popup extends Component { componentDidUpdate(prevProps, prevState) { if (this.state.open && !prevState.open) { this.updateStyles(); + this.popupMutationObserver.observe(document.documentElement, { + childList: true, + attributes: true, + subtree: true + }); if (typeof this.props.onOpen === 'function') { this.props.onOpen(); } - } else if (!this.state.open && prevState.open && typeof this.props.onClose === 'function') { - this.props.onClose(); + } else if (!this.state.open && prevState.open) { + this.popupMutationObserver.disconnect(); + if (typeof this.props.onClose === 'function') { + this.props.onClose(); + } } } + createPopupMutationObserver = () => { + let prevLabelRect = {}; + let prevMenuChildrenRect = {}; + return new MutationObserver(() => { + if (this.state.open) { + const labelRect = this.labelRef.current.getBoundingClientRect(); + const menuChildrenRect = this.menuChildrenRef.current.getBoundingClientRect(); + if (labelRect.x !== prevLabelRect.x || + labelRect.y !== prevLabelRect.y || + labelRect.width !== prevLabelRect.width || + labelRect.height !== prevLabelRect.height || + menuChildrenRect.x !== prevMenuChildrenRect.x || + menuChildrenRect.y !== prevMenuChildrenRect.y || + menuChildrenRect.width !== prevMenuChildrenRect.width || + menuChildrenRect.height !== prevMenuChildrenRect.height) { + this.updateStyles(); + } + + prevLabelRect = labelRect; + prevMenuChildrenRect = menuChildrenRect; + } else { + prevLabelRect = {}; + prevMenuChildrenRect = {}; + } + }); + } + updateStyles = () => { + this.menuContainerRef.current.removeAttribute('style'); + this.menuScrollRef.current.removeAttribute('style'); + this.menuBorderTopRef.current.removeAttribute('style'); + this.menuBorderRightRef.current.removeAttribute('style'); + this.menuBorderBottomRef.current.removeAttribute('style'); + this.menuBorderLeftRef.current.removeAttribute('style'); + this.labelBorderTopRef.current.removeAttribute('style'); + this.labelBorderRightRef.current.removeAttribute('style'); + this.labelBorderBottomRef.current.removeAttribute('style'); + this.labelBorderLeftRef.current.removeAttribute('style'); + const menuDirections = {}; const bodyRect = document.body.getBoundingClientRect(); - const menuRect = this.menuRef.current.getBoundingClientRect(); const labelRect = this.labelRef.current.getBoundingClientRect(); + const menuChildredRect = this.menuChildrenRef.current.getBoundingClientRect(); const borderSize = parseFloat(window.getComputedStyle(this.hiddenBorderRef.current).getPropertyValue('border-top-width')); const labelPosition = { left: labelRect.x - bodyRect.x, @@ -69,38 +118,38 @@ class Popup extends Component { bottom: (bodyRect.height + bodyRect.y) - (labelRect.y + labelRect.height) }; - if (menuRect.height <= labelPosition.bottom) { - this.menuRef.current.style.top = `${labelPosition.top + labelRect.height}px`; + if (menuChildredRect.height <= labelPosition.bottom) { + this.menuContainerRef.current.style.top = `${labelPosition.top + labelRect.height}px`; this.menuScrollRef.current.style.maxHeight = `${labelPosition.bottom}px`; menuDirections.bottom = true; - } else if (menuRect.height <= labelPosition.top) { - this.menuRef.current.style.bottom = `${labelPosition.bottom + labelRect.height}px`; + } else if (menuChildredRect.height <= labelPosition.top) { + this.menuContainerRef.current.style.bottom = `${labelPosition.bottom + labelRect.height}px`; this.menuScrollRef.current.style.maxHeight = `${labelPosition.top}px`; menuDirections.top = true; } else if (labelPosition.bottom >= labelPosition.top) { - this.menuRef.current.style.top = `${labelPosition.top + labelRect.height}px`; + this.menuContainerRef.current.style.top = `${labelPosition.top + labelRect.height}px`; this.menuScrollRef.current.style.maxHeight = `${labelPosition.bottom}px`; menuDirections.bottom = true; } else { - this.menuRef.current.style.bottom = `${labelPosition.bottom + labelRect.height}px`; + this.menuContainerRef.current.style.bottom = `${labelPosition.bottom + labelRect.height}px`; this.menuScrollRef.current.style.maxHeight = `${labelPosition.top}px`; menuDirections.top = true; } - if (menuRect.width <= (labelPosition.right + labelRect.width)) { - this.menuRef.current.style.left = `${labelPosition.left}px`; + if (menuChildredRect.width <= (labelPosition.right + labelRect.width)) { + this.menuContainerRef.current.style.left = `${labelPosition.left}px`; this.menuScrollRef.current.style.maxWidth = `${labelPosition.right + labelRect.width}px`; menuDirections.right = true; - } else if (menuRect.width <= (labelPosition.left + labelRect.width)) { - this.menuRef.current.style.right = `${labelPosition.right}px`; + } else if (menuChildredRect.width <= (labelPosition.left + labelRect.width)) { + this.menuContainerRef.current.style.right = `${labelPosition.right}px`; this.menuScrollRef.current.style.maxWidth = `${labelPosition.left + labelRect.width}px`; menuDirections.left = true; } else if (labelPosition.right > labelPosition.left) { - this.menuRef.current.style.left = `${labelPosition.left}px`; + this.menuContainerRef.current.style.left = `${labelPosition.left}px`; this.menuScrollRef.current.style.maxWidth = `${labelPosition.right + labelRect.width}px`; menuDirections.right = true; } else { - this.menuRef.current.style.right = `${labelPosition.right}px`; + this.menuContainerRef.current.style.right = `${labelPosition.right}px`; this.menuScrollRef.current.style.maxWidth = `${labelPosition.left + labelRect.width}px`; menuDirections.left = true; } @@ -128,14 +177,14 @@ class Popup extends Component { this.labelBorderLeftRef.current.style.left = `${labelPosition.left}px`; if (menuDirections.top) { - this.labelBorderTopRef.current.style.display = 'none'; + this.labelBorderTopRef.current.style.left = `${labelPosition.left + menuChildredRect.width}px`; if (menuDirections.left) { this.menuBorderBottomRef.current.style.right = `${labelRect.width - borderSize}px`; } else { this.menuBorderBottomRef.current.style.left = `${labelRect.width - borderSize}px`; } } else { - this.labelBorderBottomRef.current.style.display = 'none'; + this.labelBorderBottomRef.current.style.left = `${labelPosition.left + menuChildredRect.width}px`; if (menuDirections.left) { this.menuBorderTopRef.current.style.right = `${labelRect.width - borderSize}px`; } else { @@ -144,7 +193,7 @@ class Popup extends Component { } } - this.menuRef.current.style.visibility = 'visible'; + this.menuContainerRef.current.style.visibility = 'visible'; } onKeyUp = (event) => { @@ -177,9 +226,11 @@ class Popup extends Component { return ( -
-
- {children} +
+
+
+ {children} +
diff --git a/src/common/Popup/styles.less b/src/common/Popup/styles.less index 9f542e699..daa8bacf6 100644 --- a/src/common/Popup/styles.less +++ b/src/common/Popup/styles.less @@ -2,7 +2,7 @@ position: absolute; visibility: hidden; - .scroll-container { + .menu-scroll-container { overflow: auto; } } diff --git a/src/routes/Board/Board.js b/src/routes/Board/Board.js index 4b23257a3..feb30a9be 100644 --- a/src/routes/Board/Board.js +++ b/src/routes/Board/Board.js @@ -1,42 +1,10 @@ import React, { PureComponent } from 'react'; -import { Catalogs } from 'stremio-aggregators'; -import { addons } from 'stremio-services'; -import { Stream } from 'stremio-common'; -import { Video } from 'stremio-common'; -import { LibraryItemList } from 'stremio-common'; -import { MetaItem } from 'stremio-common'; -import { Addon } from 'stremio-common'; -import { ShareAddon } from 'stremio-common'; -import { UserPanel } from 'stremio-common'; class Board extends PureComponent { - constructor(props) { - super(props); - - // this.aggregator = new Catalogs(addons.addons); - - this.state = { - catalogs: [] - }; - } - - componentDidMount() { - // this.aggregator.evs.addListener('updated', this.onCatalogsUpdated); - // this.aggregator.run(); - } - - componentWillUnmount() { - // this.aggregator.evs.removeListener('updated', this.onCatalogsUpdated); - } - - onCatalogsUpdated = () => { - // this.setState({ catalogs: this.aggregator.results.slice() }); - } - render() { return (
- + Board
); } diff --git a/src/routes/Detail/Detail.js b/src/routes/Detail/Detail.js index ff647ded3..3c73a7859 100644 --- a/src/routes/Detail/Detail.js +++ b/src/routes/Detail/Detail.js @@ -1,9 +1,161 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import VideosList from './VideosList'; +import Icon from 'stremio-icons/dom'; +import styles from './styles'; class Detail extends Component { + constructor(props) { + super(props); + + this.state = { + logoLoaded: true + }; + } + + shouldComponentUpdate(nextProps, nextState) { + return nextState.logoLoaded !== this.state.logoLoaded; + } + + renderSection({ title, links }) { + return ( +
+ { + title ? +
{title}
+ : + null + } + {links.map(link => {link})} +
+ ); + } + render() { - return null; + return ( +
0 ? { backgroundImage: 'url(' + this.props.metaItem.background + ')' } : { backgroundColor: colors.backgrounddarker }} className={styles['detail-container']}> +
+
+ { + this.state.logoLoaded ? + this.setState({ logoLoaded: false })} /> + : + null + } +
{this.props.metaItem.duration}
+
+ { + this.props.metaItem.releaseInfo.length > 0 ? + this.props.metaItem.releaseInfo + : + this.props.metaItem.released.getFullYear() + } +
+
{this.props.metaItem.name}
+
{this.props.metaItem.description}
+ {this.renderSection({ title: 'GENRES', links: this.props.metaItem.genres })} + {this.renderSection({ title: 'WRITTEN BY', links: this.props.metaItem.writers })} + {this.renderSection({ title: 'DIRECTED BY', links: this.props.metaItem.directors })} + {this.renderSection({ title: 'CAST', links: this.props.metaItem.cast })} +
+ + +
Trailer
+
+ + +
{this.props.metaItem.imdbRating} / 10
+
+
+ +
{this.props.inLibrary ? 'Remove from Library' : 'Add to library'}
+
+
+ +
Share
+
+
+
+ +
+ ); } } +Detail.propTypes = { + inLibrary: PropTypes.bool.isRequired, + metaItem: PropTypes.object.isRequired, + toggleLibraryButton: PropTypes.func +}; +Detail.defaultProps = { + inLibrary: false, + metaItem: { + logo: 'https://images.metahub.space/logo/medium/tt4123430/img', + background: 'https://images.metahub.space/background/medium/tt4123430/img', + duration: '134 min', + releaseInfo: '2018', + released: new Date(2018, 4, 23), + imdbRating: '7.4', + name: 'Fantastic Beasts and Where to Find Them: The Original Screenplay', + description: 'In an effort to thwart Grindelwald' + 's plans ofraisingpurebloodwizardstoansofraisingpurebloodwizardstoansofraisingpurebloodwizardstoansofraising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards toans of raising pure-blood wizards to rule over all non-magical beings, Albus Dumbledore enlists his former student Newt Scamander, who agrees to help, unaware of the dangers that lie ahead. Lines are drawn as love and loyalty are tested, even among the truest friends and family, in an increasingly divided wizarding world.', + genres: ['Adventure', 'Family', 'Fantasy'], + writers: ['J. K. Rowling'], + directors: ['David Yates'], + cast: ['Johny Depp', 'Kevin Guthrie', 'Carmen Ejogo', 'Wolf Roth'], + videos: [ + { id: '1', poster: 'https://www.stremio.com/websiste/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '2', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 3 }, + { id: '3', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 5 }, + { id: '4', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 4 }, + { id: '5', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 }, + { id: '6', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '7', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 }, + { id: '8', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 }, + { id: '9', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 }, + { id: '10', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 }, + { id: '11', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '12', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '13', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 }, + { id: '14', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 }, + { id: '15', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 }, + { id: '16', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 }, + { id: '17', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '18', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '19', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 }, + { id: '20', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 }, + { id: '21', poster: 'https://www.stremiocom/wsebsite/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 }, + { id: '22', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 }, + { id: '23', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '24', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '25', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 }, + { id: '26', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 3 }, + { id: '27', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 }, + { id: '28', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 5 }, + { id: '29', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '30', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '31', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 }, + { id: '32', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 1 }, + { id: '33', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 }, + { id: '34', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '35', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '36', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '37', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 }, + { id: '38', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 1 }, + { id: '39', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 }, + { id: '40', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '41', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 }, + { id: '42', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 1, name: 'The Bing BranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBranHypothesiingBran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesiing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, isWatched: true, season: 1 }, + { id: '43', poster: 'https://www.stremio.com/website/home-stremio.png', episode: 2, name: 'The Bing Bran Hypothesis', description: 'dasdasda', released: new Date(2018, 4, 23), isWatched: true, season: 1 }, + { id: '44', episode: 4, name: 'The Luminous Fish Effect', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 1 }, + { id: '45', poster: 'https://www.stremiocom/website/home-stremio.png', episode: 5, name: 'The Dumpling Paradox', description: 'dasdasda', released: new Date(2018, 4, 23), progress: 50, season: 2 }, + { id: '46', episode: 8, name: 'The Loobendfeld Decay', description: 'dasdasda', released: new Date(2018, 4, 23), isUpcoming: true, season: 1 } + ], + links: { + share: '', + imdb: 'https://www.imdb.com/title/tt4123430/?ref_=fn_al_tt_3', + youtube: '#/player' + } + } +}; + export default Detail; diff --git a/src/routes/Detail/VideosList/Video/Video.js b/src/routes/Detail/VideosList/Video/Video.js index e516fbf31..e5c2ea13e 100644 --- a/src/routes/Detail/VideosList/Video/Video.js +++ b/src/routes/Detail/VideosList/Video/Video.js @@ -87,7 +87,7 @@ const renderProgress = (progress) => { const Video = (props) => { return ( -
+
{renderPoster(props.poster)}
@@ -106,14 +106,16 @@ const Video = (props) => { Video.propTypes = { className: PropTypes.string, + id: PropTypes.string.isRequired, poster: PropTypes.string.isRequired, episode: PropTypes.number.isRequired, + season: PropTypes.number.isRequired, title: PropTypes.string.isRequired, released: PropTypes.instanceOf(Date).isRequired, isWatched: PropTypes.bool.isRequired, isUpcoming: PropTypes.bool.isRequired, progress: PropTypes.number.isRequired, - onVideoClicked: PropTypes.func + onClick: PropTypes.func }; Video.defaultProps = { poster: '', diff --git a/src/routes/Detail/VideosList/Video/styles.less b/src/routes/Detail/VideosList/Video/styles.less index 1c4b6719a..7864b0843 100644 --- a/src/routes/Detail/VideosList/Video/styles.less +++ b/src/routes/Detail/VideosList/Video/styles.less @@ -1,15 +1,6 @@ -.video-container { - --video-width: 360px; - --spacing: 8px; - --title-font-size: 12px; - --released-date-font-size: 11px; - --label-font-size: 10px; - --label-border-width: 2px; -} - .video-container { width: var(--video-width); - background-color: var(--color-backgroundlight); + background-color: var(--color-surfacedarker60); .flex-row-container { display: flex; @@ -40,21 +31,21 @@ .info-container { flex: 3; - min-height: calc(0.2 * var(--video-width)); + min-height: calc(var(--video-width) * 0.2); padding: var(--spacing); display: flex; flex-direction: column; justify-content: center; .title { - font-size: var(--title-font-size); - color: var(--color-surfacelighter); + line-height: 1.2em; + color: var(--color-surfacelight); word-break: break-all; //Firefox doesn't support { break-word } word-break: break-word; } .released-date { - font-size: var(--released-date-font-size); + font-size: 0.9em; color: var(--color-surface); } @@ -62,13 +53,13 @@ display: flex; .upcoming-label, .watched-label { - font-size: var(--label-font-size); - font-weight: 600; + font-size: 0.8em; + font-weight: 500; line-height: 1.5; - border-width: var(--label-border-width); + border-width: calc(var(--spacing) * 0.25); border-style: solid; padding: 0 0.6em; - color: var(--color-surfacelighter); + color: var(--color-surfacelight); } .upcoming-label { @@ -82,25 +73,25 @@ } >:not(:last-child) { - margin-bottom: calc(0.5 * var(--spacing)); + margin-bottom: calc(var(--spacing) * 0.5); } } .arrow-container { - width: calc(0.07 * var(--video-width)); + width: calc(var(--video-width) * 0.07); display: flex; align-items: center; padding: var(--spacing) var(--spacing) var(--spacing) 0; .arrow { width: 100%; - fill: var(--color-surfacelighter); + fill: var(--color-surfacelight); } } } .progress-container { - height: calc(0.5 * var(--spacing)); + height: calc(var(--spacing) * 0.5); background-color: var(--color-primarydark); .progress { @@ -111,6 +102,24 @@ &:hover { cursor: pointer; - background-color: var(--color-surfacelighter20); + background-color: var(--color-surfacedarker); + + .info-container { + .title { + color: var(--color-surfacelighter); + } + + .label-container { + .upcoming-label, .watched-label { + color: var(--color-surfacelighter); + } + } + } + + .arrow-container { + .arrow { + fill: var(--color-surfacelighter); + } + } } } \ No newline at end of file diff --git a/src/routes/Detail/VideosList/VideosList.js b/src/routes/Detail/VideosList/VideosList.js index 3cc1be171..c2559ad91 100644 --- a/src/routes/Detail/VideosList/VideosList.js +++ b/src/routes/Detail/VideosList/VideosList.js @@ -1,6 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import Icon from 'stremio-icons/dom'; +import { Popup } from 'stremio-common'; import Video from './Video'; import styles from './styles'; @@ -8,20 +10,25 @@ class VideosList extends Component { constructor(props) { super(props); + this.seasonsPopupRef = React.createRef(); this.seasons = this.props.videos.map((video) => video.season) .filter((season, index, seasons) => seasons.indexOf(season) === index); this.state = { - selectedSeason: this.seasons[0] + selectedSeason: this.seasons[0], + selectedVideoId: 0, + seasonsPopupOpen: false } } changeSeason = (event) => { - this.setState({ selectedSeason: parseInt(event.target.value) }); + this.setState({ selectedSeason: parseInt(event.currentTarget.dataset.season) }); + this.seasonsPopupRef.current && this.seasonsPopupRef.current.close(); } shouldComponentUpdate(nextProps, nextState) { - return nextState.selectedSeason !== this.state.selectedSeason; + return nextState.selectedSeason !== this.state.selectedSeason || + nextState.seasonsPopupOpen !== this.state.seasonsPopupOpen; } onPrevButtonClicked = () => { @@ -34,20 +41,45 @@ class VideosList extends Component { this.setState({ selectedSeason: this.seasons[nextSeasonIndex] }); } + onSeasonsPopupOpen = () => { + this.setState({ seasonsPopupOpen: true }); + } + + onSeasonsPopupClose = () => { + this.setState({ seasonsPopupOpen: false }); + } + + onClick = (event) => { + this.setState({ selectedVideoId: event.currentTarget.dataset.videoId }); + console.log(event.currentTarget.dataset.videoId); + } + render() { return ( -
+
- + + +
+
Season
+
{this.state.selectedSeason}
+ +
+
+ +
+ {this.seasons.map((season) => +
+
Season
+
{season}
+
+ )} +
+
+
@@ -58,13 +90,16 @@ class VideosList extends Component { .map((video) =>
@@ -74,6 +109,7 @@ class VideosList extends Component { } VideosList.propTypes = { + className: PropTypes.string, videos: PropTypes.arrayOf(PropTypes.object).isRequired }; VideosList.defaultProps = { diff --git a/src/routes/Detail/VideosList/styles.less b/src/routes/Detail/VideosList/styles.less index 41f242d3b..f52f596d6 100644 --- a/src/routes/Detail/VideosList/styles.less +++ b/src/routes/Detail/VideosList/styles.less @@ -1,24 +1,69 @@ .videos-list-container { - --scroll-container-width: 392px; - --seasons-bar-height: 50px; - --spacing: 8px; -} - -.videos-list-container { - height: 100%; - display: inline-flex; + width: calc(var(--video-width) + var(--spacing) * 6); + display: flex; flex-direction: column; - background: var(--color-background); + align-items: center; + background: var(--color-backgrounddarker40); .seasons-bar { - height: var(--seasons-bar-height); + height: calc(var(--video-width) * 0.14); + width: 100%; display: flex; justify-content: space-between; margin-bottom: var(--spacing); - .button-container { - width: calc(1.5 * var(--seasons-bar-height)); + .season-bar-button { cursor: pointer; + width: calc(var(--video-width) * 0.6); + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing); + color: var(--color-surfacelight); + + .season-label { + max-width: 8em; + max-height: 2.4em; + font-size: 1.2em; + line-height: 1.2em; + text-align: end; + overflow: hidden; + } + + .season-number { + margin: 0 calc(var(--spacing) * 1.5) 0 var(--spacing); + font-size: 1.2em; + line-height: 1.2em; + } + + .icon { + width: calc(var(--video-width) * 0.04); + height: calc(var(--video-width) * 0.04); + fill: var(--color-surfacelight); + } + + &:hover { + color: var(--color-surfacelighter); + background-color: var(--color-surfacedarker60); + + .icon { + fill: var(--color-surfacelighter); + } + } + + &:global(.active) { + color: var(--color-backgrounddarker); + background-color: var(--color-surfacelighter); + + .icon { + fill: var(--color-backgrounddarker); + } + } + } + + .button-container { + cursor: pointer; + width: calc(var(--video-width) * 0.2); display: flex; align-items: center; justify-content: center; @@ -26,18 +71,22 @@ .button-icon { width: 60%; height: 60%; - fill: var(--color-surfacelighter); + fill: var(--color-surfacelight); } &:hover { - background-color: var(--color-surfacelighter20); + background-color: var(--color-surfacedarker60); + + .button-icon { + fill: var(--color-surfacelighter); + } } } } .scroll-container { flex: 1; - width: var(--scroll-container-width); + width: calc(var(--video-width) + var(--spacing) * 4); padding: 0 calc(2 * var(--spacing)); margin: 0 var(--spacing); overflow-y: auto; @@ -49,14 +98,54 @@ } .scroll-container::-webkit-scrollbar { - width: var(--spacing); + width: var(--spacing) !important; } .scroll-container::-webkit-scrollbar-thumb { - background-color: var(--color-secondarylighter80); + background-color: var(--color-secondarylighter); } .scroll-container::-webkit-scrollbar-track { - background-color: var(--color-backgroundlight); + background-color: var(--color-backgroundlighter); + } +} + +:global(.detail-popup-container) { + --border-color: var(--color-backgrounddarker80); + + .popup-content { + width: calc(var(--video-width) * 0.6); + background-color: var(--color-surfacelighter); + + .season { + cursor: pointer; + display: flex; + align-items: center; + width: 100%; + padding: calc(var(--spacing) * 1.5); + font-size: 1.2em; + color: var(--color-backgrounddark); + + .season-label { + max-height: 2.4em; + line-height: 1.2em; + overflow: hidden; + } + + .season-number { + margin-left: var(--spacing); + line-height: 1.2em; + } + + &.selected-season { + color: var(--color-surfacelighter); + background-color: var(--color-primarydark); + } + + &:hover { + color: var(--color-surfacelighter); + background-color: var(--color-primarylight); + } + } } } \ No newline at end of file diff --git a/src/routes/Detail/styles.less b/src/routes/Detail/styles.less new file mode 100644 index 000000000..c45e3e2fa --- /dev/null +++ b/src/routes/Detail/styles.less @@ -0,0 +1,175 @@ +.detail-container, :global(.detail-popup-container) { + --spacing: 8px; + --action-button-width: 80px; + --video-width: 360px; + --stream-width: 360px; + font-size: 12px; +} + +.detail-container { + position: relative; + z-index: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + + .overlay-container { + position: absolute; + z-index: 0; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--color-backgrounddarker60); + } + + .info-container { + position: absolute; + z-index: 1; + top: 0; + left: 0; + right: 40%; + bottom: 0; + padding: calc(var(--spacing) * 3); + + .logo { + display: block; + height: calc(var(--action-button-width) * 1.2); + object-fit: contain; + } + + .logo-error { + display: none; + } + + .duration { + display: inline-block; + max-width: 45%; + margin-right: 1.2em; + font-size: 1.15em; + overflow: hidden; + white-space: pre; + text-overflow: ellipsis; + color: var(--color-surfacelight); + } + + .release-info { + display: inline-block; + max-width: 45%; + font-size: 1.15em; + overflow: hidden; + white-space: pre; + text-overflow: ellipsis; + color: var(--color-surfacelight); + } + + .name { + max-height: 3em; + overflow: hidden; + overflow-wrap: break-word; + font-size: 1.5em; + line-height: 1.5; + color: var(--color-surfacelight); + } + + .description { + max-height: 10.5em; + overflow: hidden; + overflow-wrap: break-word; + line-height: 1.5; + color: var(--color-surfacelight); + } + + .section-container { + max-height: 3.2em; + overflow: hidden; + + .title { + margin-bottom: 0.3em; + font-size: 1.15em; + color: var(--color-surface); + } + + .link { + display: none; + max-width: 100%; + padding: 0.3em 0.6em; + font-size: 1.15em; + overflow: hidden; + white-space: pre; + text-overflow: ellipsis; + color: var(--color-surfacelight); + cursor: pointer; + + &:hover { + color: var(--color-surfacelighter); + background-color: var(--color-surface40); + } + + &:nth-child(-n+6) { + display: inline-block; + } + } + } + + .action-buttons-container { + position: absolute; + left: calc(var(--spacing) * 3); + bottom: calc(var(--spacing) * 3); + + .action-button-container { + cursor: pointer; + width: var(--action-button-width); + height: var(--action-button-width); + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + + .icon { + height: 30%; + margin: 10% 0; + fill: var(--color-surfacelight); + } + + .label { + height: 2.4em; + padding: 0 1em; + overflow-wrap: break-word; + overflow: hidden; + font-size: 1.05em; + line-height: 1.2em; + color: var(--color-surfacelight); + } + + &:hover { + background-color: var(--color-surfacedarker60); + + .icon { + fill: var(--color-surfacelighter); + } + + .label { + color: var(--color-surfacelighter); + } + } + } + } + + >:not(:last-child) { + margin-bottom: calc(var(--spacing) * 1.5); + } + } + + .videos-list { + position: absolute; + z-index: 2; + top: 0; + right: 0; + bottom: 0; + padding: calc(3 * var(--spacing)) 0; + } +} \ No newline at end of file diff --git a/src/routes/Player/BufferingLoader/BufferingLoader.js b/src/routes/Player/BufferingLoader/BufferingLoader.js new file mode 100644 index 000000000..113719174 --- /dev/null +++ b/src/routes/Player/BufferingLoader/BufferingLoader.js @@ -0,0 +1,25 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import styles from './styles'; + +class BufferingLoader extends PureComponent { + render() { + if (!this.props.buffering) { + return null; + } + + return ( +
+
+
+ ); + } +} + +BufferingLoader.propTypes = { + className: PropTypes.string, + buffering: PropTypes.bool +}; + +export default BufferingLoader; diff --git a/src/routes/Player/BufferingLoader/index.js b/src/routes/Player/BufferingLoader/index.js new file mode 100644 index 000000000..cc509df35 --- /dev/null +++ b/src/routes/Player/BufferingLoader/index.js @@ -0,0 +1,3 @@ +import BufferingLoader from './BufferingLoader'; + +export default BufferingLoader; diff --git a/src/routes/Player/BufferingLoader/styles.less b/src/routes/Player/BufferingLoader/styles.less new file mode 100644 index 000000000..fc7f36800 --- /dev/null +++ b/src/routes/Player/BufferingLoader/styles.less @@ -0,0 +1,24 @@ +@keyframes spin { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +.buffering-loader-container { + display: flex; + align-items: center; + justify-content: center; + + .bufferring-loader { + width: 150px; + height: 150px; + border-radius: 50%; + border: 10px solid #f3f3f3; + border-top: 10px solid #3498db; + animation: spin 2s linear infinite; + } +} \ No newline at end of file diff --git a/src/routes/Player/ControlBar/ControlBar.js b/src/routes/Player/ControlBar/ControlBar.js index f24539088..8c0f83f81 100644 --- a/src/routes/Player/ControlBar/ControlBar.js +++ b/src/routes/Player/ControlBar/ControlBar.js @@ -9,8 +9,8 @@ import VolumeBar from './VolumeBar'; import SubtitlesPicker from './SubtitlesPicker'; import styles from './styles'; -const ControlBarButton = React.forwardRef(({ icon, active, onClick }, ref) => ( -
+const ControlBarButton = React.forwardRef(({ icon, active, disabled, onClick }, ref) => ( +
)); @@ -109,15 +109,12 @@ class ControlBar extends Component { } renderSubtitlesButton() { - if (this.props.subtitleTracks.length === 0) { - return null; - } - return ( diff --git a/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js b/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js index 02d64c5b6..8d75851b3 100644 --- a/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js +++ b/src/routes/Player/ControlBar/PlayPauseButton/PlayPauseButton.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; class PlayPauseButton extends Component { shouldComponentUpdate(nextProps, nextState) { @@ -20,4 +21,14 @@ class PlayPauseButton extends Component { } } +PlayPauseButton.propTypes = { + paused: PropTypes.bool, + dispatch: PropTypes.func.isRequired, + toggleButtonComponent: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + PropTypes.shape({ render: PropTypes.func.isRequired }), + ]).isRequired +}; + export default PlayPauseButton; diff --git a/src/routes/Player/ControlBar/VolumeBar/VolumeBar.js b/src/routes/Player/ControlBar/VolumeBar/VolumeBar.js index 9d66e3a6f..98f8f8996 100644 --- a/src/routes/Player/ControlBar/VolumeBar/VolumeBar.js +++ b/src/routes/Player/ControlBar/VolumeBar/VolumeBar.js @@ -81,7 +81,11 @@ class VolumeBar extends Component { VolumeBar.propTypes = { className: PropTypes.string, volume: PropTypes.number, - toggleButtonComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired, + toggleButtonComponent: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + PropTypes.shape({ render: PropTypes.func.isRequired }), + ]).isRequired, dispatch: PropTypes.func.isRequired }; diff --git a/src/routes/Player/ControlBar/styles.less b/src/routes/Player/ControlBar/styles.less index 5cb2d305d..7216d6dc0 100644 --- a/src/routes/Player/ControlBar/styles.less +++ b/src/routes/Player/ControlBar/styles.less @@ -41,7 +41,15 @@ } } - &:hover { + &:global(.disabled) { + cursor: default; + + .icon { + fill: var(--color-surfacedark); + } + } + + &:hover:not(:global(.disabled)) { .icon { fill: var(--color-surfacelighter); } @@ -80,7 +88,7 @@ } :global(.player-popup-container) { - --border-color: var(--color-primarylight); + --border-color: var(--color-surfacelighter); .popup-content { background-color: var(--color-backgrounddark); diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 9bba9aa74..77eb23cc0 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -2,6 +2,7 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import Video from './Video'; +import BufferingLoader from './BufferingLoader'; import ControlBar from './ControlBar'; import styles from './styles'; @@ -15,6 +16,7 @@ class Player extends Component { paused: null, time: null, duration: null, + buffering: null, volume: null, subtitleTracks: [], selectedSubtitleTrackId: null, @@ -28,6 +30,7 @@ class Player extends Component { return nextState.paused !== this.state.paused || nextState.time !== this.state.time || nextState.duration !== this.state.duration || + nextState.buffering !== this.state.buffering || nextState.volume !== this.state.volume || nextState.subtitleTracks !== this.state.subtitleTracks || nextState.selectedSubtitleTrackId !== this.state.selectedSubtitleTrackId || @@ -43,6 +46,7 @@ class Player extends Component { label: 'English' }]); this.dispatch('setProp', 'selectedSubtitleTrackId', 'https://raw.githubusercontent.com/caitp/ng-media/master/example/assets/captions/bunny-en.vtt'); + this.dispatch('command', 'load', this.props.stream, {}); } onEnded = () => { @@ -71,7 +75,6 @@ class Player extends Component {
); diff --git a/src/routes/Player/Video/Video.js b/src/routes/Player/Video/Video.js index 51742c4af..af1ee9e26 100644 --- a/src/routes/Player/Video/Video.js +++ b/src/routes/Player/Video/Video.js @@ -13,19 +13,6 @@ class Video extends Component { this.video = null; } - componentDidMount() { - const Video = this.selectVideoImplementation(); - this.video = new Video(this.containerRef.current); - this.video.on('ended', this.props.onEnded); - this.video.on('error', this.props.onError); - this.video.on('propValue', this.props.onPropValue); - this.video.on('propChanged', this.props.onPropChanged); - this.video.constructor.manifest.props.forEach((propName) => { - this.dispatch('observeProp', propName); - }); - this.dispatch('command', 'load', this.props.stream, this.props.extra); - } - shouldComponentUpdate() { return false; } @@ -34,8 +21,8 @@ class Video extends Component { this.dispatch('command', 'destroy'); } - selectVideoImplementation = () => { - if (this.props.stream.ytId) { + selectVideoImplementation = (stream, extra) => { + if (stream.ytId) { return YouTubeVideo; } else { return HTMLVideo; @@ -43,6 +30,21 @@ class Video extends Component { } dispatch = (...args) => { + if (args[0] === 'command' && args[1] === 'load') { + const Video = this.selectVideoImplementation(args[2], args[3]); + if (this.video === null || this.video.constructor !== Video) { + this.dispatch('command', 'destroy'); + this.video = new Video(this.containerRef.current); + this.video.on('ended', this.props.onEnded); + this.video.on('error', this.props.onError); + this.video.on('propValue', this.props.onPropValue); + this.video.on('propChanged', this.props.onPropChanged); + this.video.constructor.manifest.props.forEach((propName) => { + this.dispatch('observeProp', propName); + }); + } + } + try { this.video && this.video.dispatch(...args); } catch (e) { @@ -59,15 +61,10 @@ class Video extends Component { Video.propTypes = { className: PropTypes.string, - stream: PropTypes.object.isRequired, - extra: PropTypes.object.isRequired, onEnded: PropTypes.func.isRequired, onError: PropTypes.func.isRequired, onPropValue: PropTypes.func.isRequired, onPropChanged: PropTypes.func.isRequired }; -Video.defaultProps = { - extra: Object.freeze({}) -}; export default Video; diff --git a/src/routes/Player/Video/stremio-video/HTMLVideo.js b/src/routes/Player/Video/stremio-video/HTMLVideo.js index 5b7f74357..bff9754d7 100644 --- a/src/routes/Player/Video/stremio-video/HTMLVideo.js +++ b/src/routes/Player/Video/stremio-video/HTMLVideo.js @@ -51,6 +51,13 @@ var HTMLVideo = function(containerElement) { return Math.floor(videoElement.duration * 1000); } + function getBuffering() { + if (!loaded) { + return null; + } + + return videoElement.readyState < videoElement.HAVE_FUTURE_DATA; + } function getVolume() { if (destroyed) { return null; @@ -140,6 +147,9 @@ var HTMLVideo = function(containerElement) { function onDurationChanged() { events.emit('propChanged', 'duration', getDuration()); } + function onBufferingChanged() { + events.emit('propChanged', 'buffering', getBuffering()); + } function onVolumeChanged() { events.emit('propChanged', 'volume', getVolume()); } @@ -216,6 +226,15 @@ var HTMLVideo = function(containerElement) { videoElement.removeEventListener('durationchange', onDurationChanged); videoElement.addEventListener('durationchange', onDurationChanged); return; + case 'buffering': + events.emit('propValue', 'buffering', getBuffering()); + videoElement.removeEventListener('waiting', onBufferingChanged); + videoElement.addEventListener('waiting', onBufferingChanged); + videoElement.removeEventListener('playing', onBufferingChanged); + videoElement.addEventListener('playing', onBufferingChanged); + videoElement.removeEventListener('loadeddata', onBufferingChanged); + videoElement.addEventListener('loadeddata', onBufferingChanged); + return; case 'volume': events.emit('propValue', 'volume', getVolume()); videoElement.removeEventListener('volumechange', onVolumeChanged); @@ -383,6 +402,7 @@ var HTMLVideo = function(containerElement) { onPausedChanged(); onTimeChanged(); onDurationChanged(); + onBufferingChanged(); onSubtitleTracksChanged(); onSelectedSubtitleTrackIdChanged(); onSubtitleDelayChanged(); @@ -402,6 +422,8 @@ var HTMLVideo = function(containerElement) { onPausedChanged(); onTimeChanged(); onDurationChanged(); + onBufferingChanged(); + onSubtitleDelayChanged(); updateSubtitleText(); flushArgsQueue(); return; @@ -417,6 +439,9 @@ var HTMLVideo = function(containerElement) { videoElement.removeEventListener('timeupdate', onTimeChanged); videoElement.removeEventListener('durationchange', onDurationChanged); videoElement.removeEventListener('volumechange', onVolumeChanged); + videoElement.removeEventListener('waiting', onBufferingChanged); + videoElement.removeEventListener('playing', onBufferingChanged); + videoElement.removeEventListener('loadeddata', onBufferingChanged); containerElement.removeChild(videoElement); containerElement.removeChild(stylesElement); containerElement.removeChild(subtitlesElement); @@ -438,7 +463,7 @@ var HTMLVideo = function(containerElement) { HTMLVideo.manifest = { name: 'HTMLVideo', embedded: true, - props: ['paused', 'time', 'duration', 'volume', 'subtitleTracks', 'selectedSubtitleTrackId', 'subtitleSize', 'subtitleDelay', 'subtitleDarkBackground'] + props: ['paused', 'time', 'duration', 'volume', 'buffering', 'subtitleTracks', 'selectedSubtitleTrackId', 'subtitleSize', 'subtitleDelay', 'subtitleDarkBackground'] }; module.exports = HTMLVideo;