Skip to content

Commit 8bf4206

Browse files
brandonocaseygkatsev
authored andcommitted
fix: blob urls being ignored as valid sources (videojs#5525)
Instead of checking for blob urls in the generic updateSourceCaches method, check for blob urls inside of handleTechSourceset before updating the source cache. Fixes videojs#5504.
1 parent 401fa28 commit 8bf4206

File tree

3 files changed

+240
-10
lines changed

3 files changed

+240
-10
lines changed

src/js/player.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,13 +1379,6 @@ class Player extends Component {
13791379
type = srcObj.type;
13801380
}
13811381

1382-
// if we are a blob url, don't update the source cache
1383-
// blob urls can arise when playback is done via Media Source Extension (MSE)
1384-
// such as m3u8 sources with @videojs/http-streaming (VHS)
1385-
if (/^blob:/.test(src)) {
1386-
return;
1387-
}
1388-
13891382
// make sure all the caches are set to default values
13901383
// to prevent null checking
13911384
this.cache_.source = this.cache_.source || {};
@@ -1467,9 +1460,23 @@ class Player extends Component {
14671460
// only update the source cache when the source
14681461
// was not updated using the player api
14691462
if (!this.changingSrc_) {
1463+
let updateSourceCaches = (src) => this.updateSourceCaches_(src);
1464+
const playerSrc = this.currentSource().src;
1465+
const eventSrc = event.src;
1466+
1467+
// if we have a playerSrc that is not a blob, and a tech src that is a blob
1468+
if (playerSrc && !(/^blob:/).test(playerSrc) && (/^blob:/).test(eventSrc)) {
1469+
1470+
// if both the tech source and the player source were updated we assume
1471+
// something like @videojs/http-streaming did the sourceset and skip updating the source cache.
1472+
if (!this.lastSource_ || (this.lastSource_.tech !== eventSrc && this.lastSource_.player !== playerSrc)) {
1473+
updateSourceCaches = () => {};
1474+
}
1475+
}
1476+
14701477
// update the source to the intial source right away
14711478
// in some cases this will be empty string
1472-
this.updateSourceCaches_(event.src);
1479+
updateSourceCaches(eventSrc);
14731480

14741481
// if the `sourceset` `src` was an empty string
14751482
// wait for a `loadstart` to update the cache to `currentSrc`.
@@ -1478,7 +1485,10 @@ class Player extends Component {
14781485
if (!event.src) {
14791486
const updateCache = (e) => {
14801487
if (e.type !== 'sourceset') {
1481-
this.updateSourceCaches_(this.techGet_('currentSrc'));
1488+
const techSrc = this.techGet('currentSrc');
1489+
1490+
this.lastSource_.tech = techSrc;
1491+
this.updateSourceCaches_(techSrc);
14821492
}
14831493

14841494
this.tech_.off(['sourceset', 'loadstart'], updateCache);
@@ -1487,6 +1497,7 @@ class Player extends Component {
14871497
this.tech_.one(['sourceset', 'loadstart'], updateCache);
14881498
}
14891499
}
1500+
this.lastSource_ = {player: this.currentSource().src, tech: event.src};
14901501

14911502
this.trigger({
14921503
src: event.src,

test/unit/sourceset.test.js

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import {getAbsoluteURL} from '../../src/js/utils/url.js';
99
const Html5 = videojs.getTech('Html5');
1010
const wait = 1;
1111
let qunitFn = 'module';
12+
const blobSrc = {
13+
src: 'blob:something',
14+
type: 'video/mp4'
15+
};
1216
const testSrc = {
1317
src: 'http://vjs.zencdn.net/v/oceans.mp4',
1418
type: 'video/mp4'
@@ -132,6 +136,20 @@ QUnit[qunitFn]('sourceset', function(hooks) {
132136
});
133137
});
134138

139+
QUnit.test('data-setup one blob', function(assert) {
140+
const done = assert.async();
141+
142+
this.mediaEl.setAttribute('data-setup', JSON.stringify({sources: [blobSrc]}));
143+
this.player = videojs(this.mediaEl, {
144+
enableSourceset: true
145+
});
146+
147+
this.player.one('sourceset', (e) => {
148+
validateSource(this.player, [blobSrc], e);
149+
done();
150+
});
151+
});
152+
135153
QUnit.test('data-setup preload auto', function(assert) {
136154
const done = assert.async();
137155

@@ -175,6 +193,20 @@ QUnit[qunitFn]('sourceset', function(hooks) {
175193
});
176194
});
177195

196+
QUnit.test('videojs({sources: [...]}) one blob', function(assert) {
197+
const done = assert.async();
198+
199+
this.player = videojs(this.mediaEl, {
200+
enableSourceset: true,
201+
sources: [blobSrc]
202+
});
203+
204+
this.player.one('sourceset', (e) => {
205+
validateSource(this.player, [blobSrc], e);
206+
done();
207+
});
208+
});
209+
178210
QUnit.test('videojs({sources: [...]}) two sources', function(assert) {
179211
const done = assert.async();
180212

@@ -203,6 +235,20 @@ QUnit[qunitFn]('sourceset', function(hooks) {
203235
});
204236
});
205237

238+
QUnit.test('mediaEl.src = blob;', function(assert) {
239+
const done = assert.async();
240+
241+
this.mediaEl.src = blobSrc.src;
242+
this.player = videojs(this.mediaEl, {
243+
enableSourceset: true
244+
});
245+
246+
this.player.one('sourceset', (e) => {
247+
validateSource(this.player, [{src: blobSrc.src, type: ''}], e);
248+
done();
249+
});
250+
});
251+
206252
QUnit.test('mediaEl.setAttribute("src", ...)"', function(assert) {
207253
const done = assert.async();
208254

@@ -217,6 +263,20 @@ QUnit[qunitFn]('sourceset', function(hooks) {
217263
});
218264
});
219265

266+
QUnit.test('mediaEl.setAttribute("src", blob)', function(assert) {
267+
const done = assert.async();
268+
269+
this.mediaEl.setAttribute('src', blobSrc.src);
270+
this.player = videojs(this.mediaEl, {
271+
enableSourceset: true
272+
});
273+
274+
this.player.one('sourceset', (e) => {
275+
validateSource(this.player, [{src: blobSrc.src, type: ''}], e);
276+
done();
277+
});
278+
});
279+
220280
QUnit.test('<source> one source', function(assert) {
221281
const done = assert.async();
222282

@@ -343,6 +403,20 @@ QUnit[qunitFn]('sourceset', function(hooks) {
343403
this.player.src(testSrc);
344404
});
345405

406+
QUnit.test('player.src({...}) one blob', function(assert) {
407+
const done = assert.async();
408+
409+
this.player = videojs(this.mediaEl, {
410+
enableSourceset: true
411+
});
412+
this.player.one('sourceset', (e) => {
413+
validateSource(this.player, [blobSrc], e);
414+
done();
415+
});
416+
417+
this.player.src(blobSrc);
418+
});
419+
346420
QUnit.test('player.src({...}) preload auto', function(assert) {
347421
const done = assert.async();
348422

@@ -387,6 +461,19 @@ QUnit[qunitFn]('sourceset', function(hooks) {
387461
this.player.tech_.el_.src = testSrc.src;
388462
});
389463

464+
QUnit.test('mediaEl.src = blob', function(assert) {
465+
const done = assert.async();
466+
467+
this.player = videojs(this.mediaEl, {enableSourceset: true});
468+
469+
this.player.one('sourceset', (e) => {
470+
validateSource(this.player, [{src: blobSrc.src, type: ''}], e);
471+
done();
472+
});
473+
474+
this.player.tech_.el_.src = blobSrc.src;
475+
});
476+
390477
QUnit.test('mediaEl.setAttribute("src", ...)"', function(assert) {
391478
const done = assert.async();
392479

@@ -400,6 +487,19 @@ QUnit[qunitFn]('sourceset', function(hooks) {
400487
this.player.tech_.el_.setAttribute('src', testSrc.src);
401488
});
402489

490+
QUnit.test('mediaEl.setAttribute("src", blob)"', function(assert) {
491+
const done = assert.async();
492+
493+
this.player = videojs(this.mediaEl, {enableSourceset: true});
494+
495+
this.player.one('sourceset', (e) => {
496+
validateSource(this.player, [{src: blobSrc.src, type: ''}], e);
497+
done();
498+
});
499+
500+
this.player.tech_.el_.setAttribute('src', blobSrc.src);
501+
});
502+
403503
const appendTypes = [
404504
{name: 'appendChild', fn: (el, obj) => el.appendChild(obj)},
405505
{name: 'innerHTML', fn: (el, obj) => {el.innerHTML = obj.outerHTML;}}, // eslint-disable-line
@@ -672,6 +772,113 @@ QUnit[qunitFn]('sourceset', function(hooks) {
672772
this.player.src(testSrc);
673773
});
674774

775+
QUnit.test('hls -> hls -> blob -> hls', function(assert) {
776+
this.totalSourcesets = 5;
777+
// we have to force techFaker here as some browsers, ie edge/safari support
778+
// native HLS.
779+
this.player.options_.techOrder = ['techFaker'];
780+
this.player.options_.techFaker = this.player.options_.techFaker || {};
781+
const done = assert.async();
782+
const m3u8One = {
783+
src: 'http://vjs.zencdn.net/v/oceans.m3u8',
784+
type: 'application/x-mpegURL'
785+
};
786+
const blobOne = 'blob:one';
787+
const m3u8Two = {
788+
src: 'http://vjs.zencdn.net/v/oceans-two.m3u8',
789+
type: 'application/x-mpegURL'
790+
};
791+
const blobTwo = 'blob:two';
792+
const setTechFaker = (src) => {
793+
this.player.options_.techFaker = this.player.options_.techFaker || {};
794+
this.player.tech_.options_ = this.player.tech_.options_ || {};
795+
this.player.tech_.options_.sourceset = src;
796+
this.player.options_.techFaker.sourceset = src;
797+
};
798+
799+
this.player.one('sourceset', (e1) => {
800+
validateSource(this.player, [m3u8One], e1, {event: blobOne, attr: blobOne, prop: blobOne});
801+
802+
this.player.one('sourceset', (e2) => {
803+
validateSource(this.player, [m3u8Two], e2, {event: blobTwo, attr: blobTwo, prop: blobTwo});
804+
805+
// should change to blobSrc now
806+
this.player.one('sourceset', (e3) => {
807+
validateSource(this.player, [blobSrc], e3);
808+
809+
this.player.one('sourceset', (e4) => {
810+
validateSource(this.player, [m3u8Two], e2, {event: blobTwo, attr: blobTwo, prop: blobTwo});
811+
812+
done();
813+
});
814+
815+
setTechFaker(blobTwo);
816+
this.player.src(m3u8Two);
817+
});
818+
819+
setTechFaker(blobSrc.src);
820+
this.player.src(blobSrc);
821+
});
822+
823+
setTechFaker(blobTwo);
824+
this.player.src(m3u8Two);
825+
});
826+
827+
setTechFaker(blobOne);
828+
this.player.src(m3u8One);
829+
});
830+
831+
QUnit.test('hls -> mp4 -> hls -> blob', function(assert) {
832+
this.totalSourcesets = 5;
833+
// we have to force techFaker here as some browsers, ie edge/safari support
834+
// native HLS.
835+
this.player.options_.techOrder = ['techFaker'];
836+
this.player.options_.techFaker = this.player.options_.techFaker || {};
837+
const done = assert.async();
838+
const m3u8One = {
839+
src: 'http://vjs.zencdn.net/v/oceans.m3u8',
840+
type: 'application/x-mpegURL'
841+
};
842+
const blobOne = 'blob:one';
843+
const setTechFaker = (src) => {
844+
this.player.options_.techFaker = this.player.options_.techFaker || {};
845+
this.player.tech_.options_ = this.player.tech_.options_ || {};
846+
this.player.tech_.options_.sourceset = src;
847+
this.player.options_.techFaker.sourceset = src;
848+
};
849+
850+
this.player.one('sourceset', (e1) => {
851+
validateSource(this.player, [m3u8One], e1, {event: blobOne, attr: blobOne, prop: blobOne});
852+
853+
this.player.one('sourceset', (e2) => {
854+
validateSource(this.player, [testSrc], e2);
855+
856+
// should change to blobSrc now
857+
this.player.one('sourceset', (e3) => {
858+
validateSource(this.player, [m3u8One], e3, {event: blobOne, attr: blobOne, prop: blobOne});
859+
860+
this.player.one('sourceset', (e4) => {
861+
validateSource(this.player, [blobSrc], e4);
862+
863+
done();
864+
});
865+
866+
setTechFaker(blobSrc.src);
867+
this.player.src(blobSrc);
868+
});
869+
870+
setTechFaker(blobOne);
871+
this.player.src(m3u8One);
872+
});
873+
874+
setTechFaker(testSrc.src);
875+
this.player.src(testSrc);
876+
});
877+
878+
setTechFaker(blobOne);
879+
this.player.src(m3u8One);
880+
});
881+
675882
QUnit.test('player.src({...}) x2 at the same time', function(assert) {
676883
const done = assert.async();
677884

test/unit/tech/tech-faker.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ class TechFaker extends Tech {
99

1010
constructor(options, handleReady) {
1111
super(options, handleReady);
12+
13+
if (this.options_ && this.options_.sourceset) {
14+
this.fakeSourceset();
15+
}
1216
if (!options || options.autoReady !== false) {
1317
this.triggerReady();
1418
}
@@ -51,7 +55,15 @@ class TechFaker extends Tech {
5155
seeking() {
5256
return false;
5357
}
54-
src() {
58+
fakeSourceset() {
59+
this.el_.src = this.options_.sourceset;
60+
this.el_.setAttribute('src', this.options_.sourceset);
61+
super.triggerSourceset(this.options_.sourceset);
62+
}
63+
src(src) {
64+
if (typeof src !== 'undefined' && this.options_ && this.options_.sourceset) {
65+
this.fakeSourceset();
66+
}
5567
return 'movie.mp4';
5668
}
5769
currentSrc() {

0 commit comments

Comments
 (0)