Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/players/YouTube.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ export default class YouTube extends Component {
this.props.onMount && this.props.onMount(this)
}

componentWillUnmount () {
// The YouTube iframe API keeps a reference to every player it creates, so
// removing the container from the DOM is not enough to release the iframe.
// Without destroy() the iframe stays detached but reachable from window.YT,
// leaking the player and everything its event handlers close over.
if (this.player && typeof this.player.destroy === 'function') {
try {
this.player.destroy()
} catch (error) {
// destroy() throws if the iframe has already been removed
}
}
this.player = null
}

getID (url) {
if (!url || url instanceof Array || MATCH_PLAYLIST.test(url)) {
return null
Expand Down
26 changes: 26 additions & 0 deletions test/players/YouTube.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,32 @@ test('load() when ready', t => {
getSDK.restore()
})

test('componentWillUnmount() destroys the player', t => {
const destroy = sinon.fake()
const renderer = create(<YouTube url={TEST_URL} config={TEST_CONFIG} />)
const instance = renderer.getInstance()
instance.player = { destroy }
renderer.unmount()
t.ok(destroy.calledOnce, 'destroy() is called on unmount')
t.ok(instance.player === null, 'the player reference is released')
})

test('componentWillUnmount() without a player', t => {
const renderer = create(<YouTube url={TEST_URL} config={TEST_CONFIG} />)
renderer.unmount()
t.ok(true, 'unmounting before the player is ready does not throw')
})

test('componentWillUnmount() when destroy() throws', t => {
const renderer = create(<YouTube url={TEST_URL} config={TEST_CONFIG} />)
const instance = renderer.getInstance()
instance.player = {
destroy: () => { throw new Error('The YouTube player is not attached to the DOM') }
}
renderer.unmount()
t.ok(instance.player === null, 'a throwing destroy() does not break unmounting')
})

test('onStateChange() - play', t => {
const called = {}
const onPlay = () => { called.onPlay = true }
Expand Down