playable
Version:
Video player based on HTML5Video
316 lines (254 loc) • 9.53 kB
text/typescript
import { expect } from 'chai';
import * as sinon from 'sinon';
import { VideoEvent, UIEvent, EngineState, LiveState } from '../../constants';
import createPlayerTestkit, { setProperty, resetProperty } from '../../testkit';
describe('LiveStateEngine', () => {
let testkit;
let engine: any;
let liveStateEngine: any;
let eventEmitter: any;
beforeEach(() => {
testkit = createPlayerTestkit();
engine = testkit.getModule('engine');
liveStateEngine = testkit.getModule('liveStateEngine');
eventEmitter = testkit.getModule('eventEmitter');
sinon.spy(eventEmitter, 'emitAsync');
});
afterEach(() => {
eventEmitter.emitAsync.restore();
});
it('should reset state on `STATES.SRC_SET`', async function() {
const prevState = LiveState.NOT_SYNC;
liveStateEngine._setState(prevState);
expect(
liveStateEngine.state,
'not `LiveState.NONE` before `SRC_SET`',
).to.not.equal(LiveState.NONE);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.SRC_SET,
});
expect(liveStateEngine.state).to.equal(LiveState.NONE);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState,
nextState: LiveState.NONE,
},
),
'new live state emitted',
).to.equal(true);
});
describe('with dynamic content', () => {
beforeEach(() => {
setProperty(engine, 'isDynamicContent', true);
});
afterEach(() => {
resetProperty(engine, 'isDynamicContent');
});
it('should set `INITIAL` state on `METADATA_LOADED`', async function() {
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.SRC_SET,
});
expect(
liveStateEngine.state,
'`LiveState.NONE` before `METADATA_LOADED`',
).to.equal(LiveState.NONE);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.METADATA_LOADED,
});
expect(liveStateEngine.state).to.equal(LiveState.INITIAL);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState: LiveState.NONE,
nextState: LiveState.INITIAL,
},
),
'new live state emitted',
).to.equal(true);
});
describe('after `INITIAL`', () => {
beforeEach(async function() {
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.SRC_SET,
});
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.METADATA_LOADED,
});
});
it('should sync to live on `PLAY_REQUESTED`', async function() {
const syncWithLiveSpy = sinon.stub(engine, 'syncWithLive');
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.PLAY_REQUESTED,
});
expect(syncWithLiveSpy.called).to.equal(true);
syncWithLiveSpy.restore();
});
describe('on `PLAYING`', () => {
it('should set `SYNC` if `isSyncWithLive`', async function() {
setProperty(engine, 'isSyncWithLive', true);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.PLAYING,
});
expect(liveStateEngine.state).to.equal(LiveState.SYNC);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState: LiveState.INITIAL,
nextState: LiveState.SYNC,
},
),
'new live state emitted',
).to.equal(true);
resetProperty(engine, 'isSyncWithLive');
});
it('should set `NOT_SYNC` if not `isSyncWithLive`', async function() {
setProperty(engine, 'isSyncWithLive', false);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.PLAYING,
});
expect(liveStateEngine.state).to.equal(LiveState.NOT_SYNC);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState: LiveState.INITIAL,
nextState: LiveState.NOT_SYNC,
},
),
'new live state emitted',
).to.equal(true);
resetProperty(engine, 'isSyncWithLive');
});
});
});
describe('after `NOT_SYNC` on `PLAYING`', async function() {
beforeEach(() => {
liveStateEngine._setState(LiveState.NOT_SYNC);
});
it('should set `SYNC` if `isSyncWithLive`', async function() {
setProperty(engine, 'isSyncWithLive', true);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.PLAYING,
});
expect(liveStateEngine.state).to.equal(LiveState.SYNC);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState: LiveState.NOT_SYNC,
nextState: LiveState.SYNC,
},
),
'new live state emitted',
).to.equal(true);
resetProperty(engine, 'isSyncWithLive');
});
it('should ignore if not `isSyncWithLive`', async function() {
setProperty(engine, 'isSyncWithLive', false);
// reset spy state before test
eventEmitter.emitAsync.resetHistory();
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.PLAYING,
});
expect(liveStateEngine.state).to.equal(LiveState.NOT_SYNC);
// NOTE: ensure emit is not called with new `LiveState`
expect(eventEmitter.emitAsync.callCount).to.equal(1);
resetProperty(engine, 'isSyncWithLive');
});
});
describe('on `PLAYING` after seek', () => {
beforeEach(async function() {
engine._output._stateEngine.setState(EngineState.PLAYING);
liveStateEngine._setState(LiveState.SYNC);
// emulate seek by UI
await eventEmitter.emitAsync(UIEvent.PROGRESS_CHANGE);
});
it('should ignore if `isSyncWithLive`', async function() {
setProperty(engine, 'isSyncWithLive', true);
// reset spy state before test
eventEmitter.emitAsync.resetHistory();
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.PLAYING,
});
expect(liveStateEngine.state).to.equal(LiveState.SYNC);
// NOTE: ensure emit is not called with new `LiveState`
expect(eventEmitter.emitAsync.callCount).to.equal(1);
resetProperty(engine, 'isSyncWithLive');
});
it('should set `NOT_SYNC` if not `isSyncWithLive`', async function() {
setProperty(engine, 'isSyncWithLive', false);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.PLAYING,
});
expect(liveStateEngine.state).to.equal(LiveState.NOT_SYNC);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState: LiveState.SYNC,
nextState: LiveState.NOT_SYNC,
},
),
'new live state emitted',
).to.equal(true);
resetProperty(engine, 'isSyncWithLive');
});
});
it('should set `NOT_SYNC` on `PAUSE` by UI', async function() {
liveStateEngine._setState(LiveState.SYNC);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
prevState: EngineState.PLAYING,
nextState: EngineState.PAUSED,
});
expect(liveStateEngine.state).to.equal(LiveState.NOT_SYNC);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState: LiveState.SYNC,
nextState: LiveState.NOT_SYNC,
},
),
'new live state emitted',
).to.equal(true);
});
it('should set `ENDED` on stream ended', async function() {
liveStateEngine._setState(LiveState.SYNC);
await eventEmitter.emitAsync(VideoEvent.DYNAMIC_CONTENT_ENDED);
expect(liveStateEngine.state).to.equal(LiveState.ENDED);
expect(
eventEmitter.emitAsync.lastCall.calledWith(
VideoEvent.LIVE_STATE_CHANGED,
{
prevState: LiveState.SYNC,
nextState: LiveState.ENDED,
},
),
'new live state emitted',
).to.equal(true);
});
});
it('should ignore events if not `isDynamicContent`', async function() {
setProperty(engine, 'isDynamicContent', false);
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.SRC_SET,
});
expect(
liveStateEngine.state,
'`LiveState.NONE` before `METADATA_LOADED`',
).to.equal(LiveState.NONE);
// reset spy state before test
eventEmitter.emitAsync.resetHistory();
await eventEmitter.emitAsync(VideoEvent.STATE_CHANGED, {
nextState: EngineState.METADATA_LOADED,
});
expect(liveStateEngine.state).to.equal(LiveState.NONE);
// NOTE: ensure emit is not called with new `LiveState`
expect(eventEmitter.emitAsync.callCount).to.equal(1);
resetProperty(engine, 'isDynamicContent');
});
});