diff --git a/packages/obonode/obojobo-chunks-youtube/youtube-player.js b/packages/obonode/obojobo-chunks-youtube/youtube-player.js index 961dc03b7c..c00d550f99 100644 --- a/packages/obonode/obojobo-chunks-youtube/youtube-player.js +++ b/packages/obonode/obojobo-chunks-youtube/youtube-player.js @@ -10,6 +10,8 @@ const { uuid } = Common.util // They'll all get called once it loads const instanceCallbacksForYouTubeReady = [] +const loadedVideos = {} + // single global hangler that notifies all registered YouTubePlayer Components const onYouTubeIframeAPIReadyHandler = () => { // call every registered callback when ready @@ -22,6 +24,11 @@ class YouTubePlayer extends React.Component { this.playerId = `obojobo-draft--chunks-you-tube-player-${uuid()}` this.player = null this.loadVideo = this.loadVideo.bind(this) + this.onStateChange = this.onStateChange.bind(this) + + this.state = { + youTubePlayerId: null + } } componentDidMount() { @@ -33,6 +40,10 @@ class YouTubePlayer extends React.Component { } } + componentWillUnmount() { + delete loadedVideos[this.state.youTubePlayerId] + } + componentDidUpdate(prevProps) { // Don't update this component if the video properties haven't changed if (!this.shouldVideoUpdate(prevProps)) { @@ -79,8 +90,14 @@ class YouTubePlayer extends React.Component { playerVars: { start: startTime, end: endTime + }, + events: { + onStateChange: this.onStateChange } }) + this.setState({ youTubePlayerId: this.player.id }) + + loadedVideos[this.player.id] = this.player } shouldVideoUpdate(prevProps) { @@ -97,6 +114,18 @@ class YouTubePlayer extends React.Component { render() { return
} + + onStateChange(playerState) { + switch (playerState.data) { + case 1: // video playing + Object.values(loadedVideos).forEach(video => { + if (video.id !== playerState.target.id) { + video.pauseVideo() + } + }) + break + } + } } export default YouTubePlayer diff --git a/packages/obonode/obojobo-chunks-youtube/youtube-player.test.js b/packages/obonode/obojobo-chunks-youtube/youtube-player.test.js index 3985a3ab4d..906b8b3d31 100644 --- a/packages/obonode/obojobo-chunks-youtube/youtube-player.test.js +++ b/packages/obonode/obojobo-chunks-youtube/youtube-player.test.js @@ -20,13 +20,16 @@ describe('YouTubePlayer', () => { const tree = component.html() expect(tree).toMatchSnapshot() + + component.unmount() }) test('YouTubePlayer updates video on content change', () => { // Mock YouTube's Iframe Player object window.YT = { Player: jest.fn(() => ({ - cueVideoById: jest.fn() + cueVideoById: jest.fn(), + pauseVideo: jest.fn() })) } @@ -60,13 +63,15 @@ describe('YouTubePlayer', () => { }) expect(spy).toHaveBeenCalledTimes(4) + spy.mockRestore() }) test("YouTubePlayer doesn't update if content hasn't changed", () => { window.YT = { Player: jest.fn(() => ({ destroy: jest.fn(), - cueVideoById: jest.fn() + cueVideoById: jest.fn(), + pauseVideo: jest.fn() })) } @@ -86,5 +91,78 @@ describe('YouTubePlayer', () => { expect(spy).toHaveBeenCalledTimes(1) expect(component.instance().player.destroy).not.toHaveBeenCalled() + + spy.mockRestore() + }) + + test('Pause other players when a player is unpaused', () => { + const player1 = { + destroy: jest.fn(), + cueVideoById: jest.fn(), + id: 1, + pauseVideo: jest.fn() + } + const player2 = { + destroy: jest.fn(), + cueVideoById: jest.fn(), + id: 2, + pauseVideo: jest.fn() + } + + const mockContent1 = { + videoId: 'mockIDzz', + startTime: 1, + endTime: 2, + playerState: 2 + } + jest.mock('obojobo-document-engine/src/scripts/common/util/uuid', () => { + return () => 'mockId' + }) + const mockContent2 = { + videoId: 'mockIDzz2', + startTime: 1, + endTime: 2, + playerState: 2 + } + + window.YT = { + Player: jest.fn(() => player1), + test: 123 + } + const component1 = mount() + + window.YT = { + Player: jest.fn(() => player2), + test: 456 + } + const component2 = mount() + + component1.instance().onStateChange({ + data: 1, + target: player1 + }) + expect(player1.pauseVideo).not.toHaveBeenCalled() + expect(player2.pauseVideo).toHaveBeenCalledTimes(1) + + component1.instance().onStateChange({ + data: 1, + target: player2 + }) + expect(player2.pauseVideo).toHaveBeenCalledTimes(1) + expect(player1.pauseVideo).toHaveBeenCalledTimes(1) + + component2.instance().onStateChange({ + data: 1, + target: player1 + }) + expect(player1.pauseVideo).toHaveBeenCalledTimes(1) + expect(player2.pauseVideo).toHaveBeenCalledTimes(2) + + component2.instance().onStateChange({ + data: 1, + target: player2 + }) + expect(player1.pauseVideo).toHaveBeenCalledTimes(2) + expect(player2.pauseVideo).toHaveBeenCalledTimes(2) }) })