Skip to content

Commit

Permalink
Merge pull request video-dev#1443 from mrbar42/fix/handle-media-hijack
Browse files Browse the repository at this point in the history
Don't reset media source if replaced by 3rd party
  • Loading branch information
tjenkinson authored Nov 27, 2017
2 parents 93f3b6a + cf9ea3a commit d6f7001
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 3 deletions.
18 changes: 15 additions & 3 deletions src/controller/buffer-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class BufferController extends EventHandler {
this._msDuration = null;
// the value that we want to set mediaSource.duration to
this._levelDuration = null;
// cache the self generated object url to detect hijack of video tag
this._objectUrl = null;

// Source Buffer listeners
this.onsbue = this.onSBUpdateEnd.bind(this);
Expand Down Expand Up @@ -107,6 +109,8 @@ class BufferController extends EventHandler {
ms.addEventListener('sourceclose', this.onmsc);
// link video and media Source
media.src = URL.createObjectURL(ms);
// cache the locally generated object url
this._objectUrl = media.src;
}
}

Expand All @@ -132,13 +136,21 @@ class BufferController extends EventHandler {
// Detach properly the MediaSource from the HTMLMediaElement as
// suggested in https://github.com/w3c/media-source/issues/53.
if (this.media) {
URL.revokeObjectURL(this.media.src);
this.media.removeAttribute('src');
this.media.load();
URL.revokeObjectURL(this._objectUrl);

// clean up video tag src only if it's our own url. some external libraries might
// hijack the video tag and change its 'src' without destroying the Hls instance first
if (this.media.src === this._objectUrl) {
this.media.removeAttribute('src');
this.media.load();
} else {
logger.warn('media.src was changed by a third party - skip cleanup');
}
}

this.mediaSource = null;
this.media = null;
this._objectUrl = null;
this.pendingTracks = {};
this.tracks = {};
this.sourceBuffer = {};
Expand Down
84 changes: 84 additions & 0 deletions tests/functional/issues/video-tag-hijack.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<html>
<head>
<title>HLS.js Video tag hijack tester</title>
</head>
<body>
<!-- Swap script tags for dev/release -->
<script src="../../../dist/hls.js"></script>
<!--<script src="//localhost:8000/dist/hls.js"></script>-->

<video id="video" controls="controls" style="width:640px; height:360px; background:#000;"></video>
<div id="status">
Hijacking video tag in:
<span id="counter"></span>
</div>
<script>
(function() {
/**
* this page simulates hijacking of a video tag before destroying the existing one.
* sequence of events:
* 1. create the first instance (wait for playback)
* 2. allow 3 seconds of playback
* 3. create 2nd instance on a different stream
* 4. destroy the first instance
*/

const Hls = window.Hls;
if (!Hls.isSupported()) throw new Error('Hls.js is not supported');

const statusEl = document.querySelector('#status');
const counterEl = document.querySelector('#counter');
const video = document.querySelector('#video');
let hls1 = null;

// 1. create instance
initInstance1('http://www.streambox.fr/playlists/x31jrg1/x31jrg1.m3u8', function onPlaying() {
// 2. allow 3 seconds of playback
countDown(3000, counterEl, () => {
// 3. create another instance
initInstance2('https://nasa-i.akamaihd.net/hls/live/253565/NASA-NTV1-Public/master.m3u8');
statusEl.innerText = 'Hijacked!';

// 4. destroy the first instance
hls1.destroy();
});
});

// hoist line

function initInstance1(streamUrl, onPlaying) {
hls1 = new Hls({ debug: true });
hls1.loadSource(streamUrl);
hls1.attachMedia(video);
hls1.on(Hls.Events.MANIFEST_PARSED, function() {
video.play()
.then(() => onPlaying());
});
}

function initInstance2(streamUrl) {
console.log('------------------ Hijacking ------------------');
const hls = new Hls();
hls.loadSource(streamUrl);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
}

function countDown(time, el, cb) {
let startStamp = Date.now();
const interval = setInterval(() => {
const diff = Date.now() - startStamp;
if (diff < time) {
el.innerText = `${Math.round((time - diff) / 100) / 10}s`;
return;
}
clearInterval(interval);
cb();
}, 100);
}
}());
</script>
</body>
</html>

0 comments on commit d6f7001

Please sign in to comment.