Release v2.14.1
This commit is contained in:
1 parent
635e1a9942
commit
c3299abaac
174 files changed
+4483
-1687
No files matched your search
@@ -18,6 +18,209 @@ function loadHtmlScript(scriptPath, sandbox) {
|
||||
vm.runInNewContext(script, sandbox, { filename: scriptPath });
|
||||
}
|
||||
|
||||
test('media preload cleanup releases unpromoted video elements on every early exit', () => {
|
||||
for (const reason of ['empty', 'single', 'missing-slide', 'missing-renderer', 'missing-document']) {
|
||||
const calls = [];
|
||||
const sandbox = {
|
||||
window: {},
|
||||
mediaPreloadSignature: 'old-signature',
|
||||
mediaPreloadElements: [{
|
||||
tagName: 'VIDEO',
|
||||
pause() { calls.push('pause'); },
|
||||
removeAttribute(name) { calls.push('remove:' + name); },
|
||||
load() { calls.push('load'); }
|
||||
}],
|
||||
document: {},
|
||||
primeSlideMarkup() {}
|
||||
};
|
||||
const slides = reason === 'empty' ? [] : reason === 'single' ? [{ id: 1 }] : [{ id: 1 }, { id: 2 }];
|
||||
if (reason === 'missing-renderer') delete sandbox.primeSlideMarkup;
|
||||
if (reason === 'missing-document') delete sandbox.document;
|
||||
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
||||
sandbox.syncSlideMediaPreloads(slides, reason === 'missing-slide' ? 0.5 : 0);
|
||||
assert.deepEqual(calls, ['pause', 'remove:src', 'load'], reason);
|
||||
assert.equal(sandbox.mediaPreloadSignature, '');
|
||||
assert.equal(sandbox.mediaPreloadElements.length, 0);
|
||||
sandbox.syncSlideMediaPreloads([], 0);
|
||||
assert.equal(calls.length, 3);
|
||||
}
|
||||
});
|
||||
|
||||
test('warmed video node is promoted into its slide instead of fetched again', () => {
|
||||
const createdVideos = [];
|
||||
let nextSource = '';
|
||||
const holder = {
|
||||
innerHTML: '',
|
||||
querySelectorAll() {
|
||||
return [{ tagName: 'VIDEO', getAttribute(name) { return name === 'data-pulse-video-src' ? nextSource : null; } }];
|
||||
}
|
||||
};
|
||||
const sandbox = {
|
||||
window: { innerWidth: 1920, innerHeight: 1080, location: { origin: 'http://localhost' } },
|
||||
URL,
|
||||
currentPlaylistSignature: 'playlist',
|
||||
mediaPreloadSignature: '',
|
||||
mediaPreloadElements: [],
|
||||
primeSlideMarkup(slide) {
|
||||
nextSource = '/media/slide-' + slide.id + '.mp4';
|
||||
return '<video src="' + nextSource + '"></video>';
|
||||
},
|
||||
document: {
|
||||
createElement(tag) {
|
||||
if (tag !== 'video') return holder;
|
||||
const attributes = new Map();
|
||||
const video = {
|
||||
tagName: 'VIDEO',
|
||||
preload: '',
|
||||
muted: false,
|
||||
src: '',
|
||||
loadCalls: 0,
|
||||
attributes,
|
||||
setAttribute(name, value) { attributes.set(name, String(value)); },
|
||||
getAttribute(name) { return attributes.get(name) || null; },
|
||||
removeAttribute(name) { attributes.delete(name); },
|
||||
load() { this.loadCalls += 1; },
|
||||
pause() {}
|
||||
};
|
||||
createdVideos.push(video);
|
||||
return video;
|
||||
}
|
||||
}
|
||||
};
|
||||
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
||||
|
||||
const slides = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
||||
sandbox.syncSlideMediaPreloads(slides, 0);
|
||||
const warmedVideo = createdVideos[0];
|
||||
assert.equal(warmedVideo.src, 'http://localhost/media/slide-2.mp4');
|
||||
|
||||
let promotedNode = null;
|
||||
const activeVideo = {
|
||||
tagName: 'VIDEO',
|
||||
src: 'http://localhost/media/slide-2.mp4',
|
||||
attributes: [{ name: 'class', value: 'video' }, { name: 'data-pulse-video-src', value: '/media/slide-2.mp4' }],
|
||||
getAttribute(name) { return name === 'data-pulse-video-src' ? '/media/slide-2.mp4' : null; },
|
||||
parentNode: {
|
||||
replaceChild(replacement, oldNode) {
|
||||
assert.equal(oldNode, activeVideo);
|
||||
promotedNode = replacement;
|
||||
}
|
||||
}
|
||||
};
|
||||
const root = { querySelectorAll() { return [activeVideo]; } };
|
||||
|
||||
assert.equal(sandbox.promotePreloadedVideoElements(root, 2), 1);
|
||||
assert.equal(promotedNode, warmedVideo);
|
||||
assert.equal(warmedVideo.loadCalls, 1);
|
||||
|
||||
sandbox.syncSlideMediaPreloads(slides, 1);
|
||||
assert.equal(createdVideos[1].src, 'http://localhost/media/slide-3.mp4');
|
||||
assert.notEqual(sandbox.mediaPreloadElements[0], warmedVideo);
|
||||
});
|
||||
|
||||
test('media preloads next-slide images without creating duplicate video loads', () => {
|
||||
const calls = [];
|
||||
const nextImage = {};
|
||||
Object.defineProperty(nextImage, 'src', { set(value) { calls.push(value); } });
|
||||
const holder = {
|
||||
innerHTML: '',
|
||||
querySelectorAll(selector) {
|
||||
assert.equal(selector, 'img[src], video[data-pulse-video-src]');
|
||||
return [{ tagName: 'IMG', getAttribute() { return '/media/next.jpg'; } }];
|
||||
}
|
||||
};
|
||||
const sandbox = {
|
||||
window: { innerWidth: 1920, innerHeight: 1080, location: { origin: 'http://localhost' } },
|
||||
URL,
|
||||
currentPlaylistSignature: 'playlist',
|
||||
mediaPreloadSignature: '',
|
||||
mediaPreloadElements: [],
|
||||
primeSlideMarkup() { return '<img src="/media/next.jpg"><video src="/media/next.mp4"></video>'; },
|
||||
Image: function () { return nextImage; },
|
||||
document: {
|
||||
createElement() { return holder; }
|
||||
}
|
||||
};
|
||||
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
||||
const slides = [{ id: 1 }, { id: 2 }];
|
||||
sandbox.syncSlideMediaPreloads(slides, 0);
|
||||
assert.deepEqual(calls, ['http://localhost/media/next.jpg']);
|
||||
assert.equal(sandbox.mediaPreloadElements[0], nextImage);
|
||||
sandbox.syncSlideMediaPreloads(slides, 0);
|
||||
assert.equal(calls.length, 1);
|
||||
});
|
||||
|
||||
test('media preloader keeps a warmed next-slide video node for promotion', () => {
|
||||
const preloadedVideo = {
|
||||
tagName: 'VIDEO',
|
||||
loadCalls: 0,
|
||||
load() { this.loadCalls += 1; }
|
||||
};
|
||||
const sourceVideo = { tagName: 'VIDEO', getAttribute() { return '/media/next.mp4'; } };
|
||||
const holder = {
|
||||
innerHTML: '',
|
||||
querySelectorAll(selector) {
|
||||
assert.equal(selector, 'img[src], video[data-pulse-video-src]');
|
||||
return [sourceVideo];
|
||||
}
|
||||
};
|
||||
const sandbox = {
|
||||
window: { innerWidth: 1920, innerHeight: 1080, location: { origin: 'http://localhost' } },
|
||||
URL,
|
||||
currentPlaylistSignature: 'playlist',
|
||||
mediaPreloadSignature: '',
|
||||
mediaPreloadElements: [],
|
||||
primeSlideMarkup() { return '<video src="/media/next.mp4"></video>'; },
|
||||
document: {
|
||||
createElement(tag) { return tag === 'video' ? preloadedVideo : holder; }
|
||||
}
|
||||
};
|
||||
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
||||
sandbox.syncSlideMediaPreloads([{ id: 1 }, { id: 2 }], 0);
|
||||
|
||||
assert.equal(preloadedVideo.src, 'http://localhost/media/next.mp4');
|
||||
assert.equal(preloadedVideo.preload, 'auto');
|
||||
assert.equal(preloadedVideo.loadCalls, 1);
|
||||
assert.equal(sandbox.mediaPreloadElements[0], preloadedVideo);
|
||||
assert.equal(preloadedVideo.__pulsePreloadSlideId, '2');
|
||||
});
|
||||
|
||||
test('offline playlist snapshots preserve and apply the RTMP skip option', () => {
|
||||
const values = new Map();
|
||||
const sandbox = {
|
||||
window: { localStorage: {
|
||||
setItem(key, value) { values.set(key, value); },
|
||||
getItem(key) { return values.get(key) || null; }
|
||||
} },
|
||||
playlistSnapshotStorageKey: 'snapshot',
|
||||
normalizeSlide(slide) { return slide; },
|
||||
slides: [],
|
||||
currentPlaylistSignature: '',
|
||||
currentPlaylistFadeBetweenSlides: false,
|
||||
currentPlaylistSkipUnavailableRtmp: false,
|
||||
currentPlaylistEtag: '',
|
||||
pendingPlaylistUpdate: null,
|
||||
activeSlidesCacheKey: '', activeSlidesCacheValue: [],
|
||||
slideMarkupCache: {}, templateLayoutCache: {}, templateRenderPlanCache: {},
|
||||
renderCacheViewportKey: '', index: 0
|
||||
};
|
||||
loadScript(path.join(__dirname, '..', 'src', 'player', 'public', 'js', 'player-page-playlist.js'), sandbox);
|
||||
for (const skipUnavailableRtmp of [true, false]) {
|
||||
sandbox.savePlaylistSnapshot({
|
||||
slides: [{ id: 1 }], signature: 'revision', fadeBetweenSlides: true,
|
||||
skipUnavailableRtmp, etag: '"etag"'
|
||||
});
|
||||
const snapshot = sandbox.loadPlaylistSnapshot();
|
||||
assert.equal(snapshot.skipUnavailableRtmp, skipUnavailableRtmp);
|
||||
assert.equal(sandbox.applyPlaylistSnapshot(snapshot), true);
|
||||
assert.equal(sandbox.currentPlaylistSkipUnavailableRtmp, skipUnavailableRtmp);
|
||||
assert.equal(sandbox.currentPlaylistFadeBetweenSlides, true);
|
||||
assert.equal(sandbox.currentPlaylistEtag, '"etag"');
|
||||
}
|
||||
values.set('snapshot', JSON.stringify({ slides: [{ id: 1 }] }));
|
||||
assert.equal(sandbox.loadPlaylistSnapshot().skipUnavailableRtmp, false);
|
||||
});
|
||||
|
||||
test('webpage preloading only targets the next slide', () => {
|
||||
const sandbox = {
|
||||
window: null,
|
||||
|
||||
Reference in new issue
Block a user