UNPKG

mock-hls-server

Version:

Fake a live/event HLS stream from a VOD one. Useful for testing.

251 lines (235 loc) 13.2 kB
const { expect } = require('chai'); const path = require('path'); const fs = require('fs'); const expressStub = () => ({ get: () => {}, listen: (_host, _port, cb) => cb() }); const proxyquire = require('proxyquire'); const MockHLSServer = proxyquire('../src/mock-hls-server', { express: expressStub }); const PLAYLIST = fs.readFileSync(path.resolve(__dirname, 'fixtures/playlist.m3u8'), 'utf8'); const VARIANT_PLAYLIST = fs.readFileSync(path.resolve(__dirname, './fixtures/master.m3u8'), 'utf8'); describe('MockHLSServer', () => { let server; afterEach(() => server = undefined); it('can be constructed with no options', () => { server = new MockHLSServer(); expect(server).to.be.ok; }); describe('_handlePlaylistResponse', () => { it('starts the stream on the first request', () => { server = new MockHLSServer(); expect(server._startTime).to.be.null; server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8'); expect(server._startTime).to.not.be.null; }); // [false, true].forEach((loop) => { [true].forEach((loop) => { describe(`with loop option ${loop ? 'enabled' : 'disabled'}`, () => { describe('in live mode', () => { beforeEach(() => server = new MockHLSServer({ loop })); describe('correcty handles a playlist response', () => { it('when no time has passed', () => { server._getTime = () => 0; expect( server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n') ).to.deep.equal([ '#EXTM3U', '#EXT-X-MEDIA-SEQUENCE:0', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:377', 'http://example.invalid/fileSequence0.ts', '' ]); }); it('when 20 seconds has passed', () => { server._getTime = () => 20; expect( server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n') ).to.deep.equal([ '#EXTM3U', '#EXT-X-MEDIA-SEQUENCE:1', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:385', 'http://example.invalid/fileSequence1.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:388', 'http://example.invalid/fileSequence2.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:378', 'http://example.invalid/fileSequence3.ts', '' ]); }); it('when 25 seconds has passed', () => { server._getTime = () => 25; expect( server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n') ).to.deep.equal([ '#EXTM3U', '#EXT-X-MEDIA-SEQUENCE:2', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:388', 'http://example.invalid/fileSequence2.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:378', 'http://example.invalid/fileSequence3.ts', ...(loop ? [ '#EXT-X-DISCONTINUITY', '#EXTINF:6.00000,', '#EXT-X-BITRATE:377', 'http://example.invalid/fileSequence0.ts', ] : []), '' ]); }); it('when 10000 seconds has passed', () => { server._getTime = () => 10000; expect( server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n') ).to.deep.equal(!loop ? [ '#EXTM3U', '#EXT-X-MEDIA-SEQUENCE:3', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:378', 'http://example.invalid/fileSequence3.ts', '' ] : [ '#EXTM3U', '#EXT-X-MEDIA-SEQUENCE:1665', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:385', 'http://example.invalid/fileSequence1.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:388', 'http://example.invalid/fileSequence2.ts', '' ]); }); }); it('correcty handles a variant playlist response', () => { expect( server._handlePlaylistResponse(VARIANT_PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n') ).to.deep.equal([ '#EXTM3U', '#EXT-X-VERSION:6', '#EXT-X-INDEPENDENT-SEGMENTS', '', '#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2218327,BANDWIDTH=2227464,CODECS="avc1.640020,mp4a.40.2",RESOLUTION=960x540,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"', 'http://localhost:8080/proxy?url=http%3A%2F%2Fexample.invalid%2Fv5%2Fprog_index.m3u8', '#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=8144656,BANDWIDTH=8178040,CODECS="avc1.64002a,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"', 'http://localhost:8080/proxy?url=http%3A%2F%2Fexample.invalid%2Fv9%2Fprog_index.m3u8', '', '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2",URI="http://localhost:8080/proxy?url=http%3A%2F%2Fexample.invalid%2Fa1%2Fprog_index.m3u8"', '' ]); }); }); describe('in event mode', () => { beforeEach(() => server = new MockHLSServer({ loop, windowSize: null })); describe('correcty handles a playlist response', () => { it('when no time has passed', () => { server._getTime = () => 0; expect( server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n') ).to.deep.equal([ '#EXTM3U', '#EXT-X-PLAYLIST-TYPE:EVENT', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:377', 'http://example.invalid/fileSequence0.ts', '' ]); }); it('when 20 seconds has passed', () => { server._getTime = () => 20; expect( server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n') ).to.deep.equal([ '#EXTM3U', '#EXT-X-PLAYLIST-TYPE:EVENT', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:377', 'http://example.invalid/fileSequence0.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:385', 'http://example.invalid/fileSequence1.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:388', 'http://example.invalid/fileSequence2.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:378', 'http://example.invalid/fileSequence3.ts', ...(!loop ? ['#EXT-X-ENDLIST'] : []), '' ]); }); it('when 10000 seconds has passed', () => { server._getTime = () => 10000; const response = server._handlePlaylistResponse(PLAYLIST, 'http://example.invalid/playlist.m3u8').split('\r\n'); if (!loop) { expect(response).to.deep.equal([ '#EXTM3U', '#EXT-X-PLAYLIST-TYPE:EVENT', '#EXT-X-TARGETDURATION:6', '#EXT-X-VERSION:3', '#EXT-X-INDEPENDENT-SEGMENTS', '#EXTINF:6.00000,', '#EXT-X-BITRATE:377', 'http://example.invalid/fileSequence0.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:385', 'http://example.invalid/fileSequence1.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:388', 'http://example.invalid/fileSequence2.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:378', 'http://example.invalid/fileSequence3.ts', '#EXT-X-ENDLIST', '' ]); } else { expect(response.length).to.equal(5423); expect(response.slice(-9)).to.deep.equal([ '#EXT-X-BITRATE:377', 'http://example.invalid/fileSequence0.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:385', 'http://example.invalid/fileSequence1.ts', '#EXTINF:6.00000,', '#EXT-X-BITRATE:388', 'http://example.invalid/fileSequence2.ts', '' ]); } }); }); }); }); }); }); });