forked from EXT/VR180-Web-Player
256 lines
6.3 KiB
JavaScript
256 lines
6.3 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { MediaController } from '../vr180player/media/media-controller.js';
|
|
|
|
function createClassList() {
|
|
const classes = new Set();
|
|
return {
|
|
add: (className) => classes.add(className),
|
|
contains: (className) => classes.has(className),
|
|
remove: (className) => classes.delete(className)
|
|
};
|
|
}
|
|
|
|
function createVideo(overrides = {}) {
|
|
const video = {
|
|
HAVE_ENOUGH_DATA: 4,
|
|
controls: true,
|
|
currentSrc: 'video.mp4',
|
|
currentTime: 20,
|
|
duration: 120,
|
|
ended: false,
|
|
loop: false,
|
|
loadCount: 0,
|
|
muted: false,
|
|
pauseCount: 0,
|
|
paused: true,
|
|
playCount: 0,
|
|
readyState: 4,
|
|
volume: 1,
|
|
load() {
|
|
this.loadCount += 1;
|
|
},
|
|
pause() {
|
|
this.pauseCount += 1;
|
|
this.paused = true;
|
|
},
|
|
play() {
|
|
this.playCount += 1;
|
|
this.paused = false;
|
|
this.ended = false;
|
|
return Promise.resolve();
|
|
},
|
|
...overrides
|
|
};
|
|
|
|
return video;
|
|
}
|
|
|
|
function createController({
|
|
is2DModeActive = () => false,
|
|
on2DPlaybackResume = () => {},
|
|
playButton = { classList: createClassList(), disabled: true },
|
|
video = createVideo()
|
|
} = {}) {
|
|
return {
|
|
controller: new MediaController({
|
|
is2DModeActive,
|
|
on2DPlaybackResume,
|
|
playButton,
|
|
video
|
|
}),
|
|
playButton,
|
|
video
|
|
};
|
|
}
|
|
|
|
test('MediaController clamps skip controls to the video bounds', () => {
|
|
const { controller, video } = createController();
|
|
|
|
controller.forward();
|
|
assert.equal(video.currentTime, 35);
|
|
|
|
video.currentTime = 118;
|
|
controller.forward();
|
|
assert.equal(video.currentTime, 120);
|
|
|
|
controller.rewind();
|
|
assert.equal(video.currentTime, 105);
|
|
|
|
video.currentTime = 4;
|
|
controller.rewind();
|
|
assert.equal(video.currentTime, 0);
|
|
});
|
|
|
|
test('MediaController seeks by progress and ignores videos without finite duration', () => {
|
|
const { controller, video } = createController();
|
|
|
|
controller.seekToProgress(0.25);
|
|
assert.equal(video.currentTime, 30);
|
|
|
|
video.duration = Number.POSITIVE_INFINITY;
|
|
controller.seekToProgress(0.75);
|
|
assert.equal(video.currentTime, 30);
|
|
});
|
|
|
|
test('MediaController toggles mute and native controls', () => {
|
|
const { controller, video } = createController();
|
|
|
|
controller.toggleMute();
|
|
assert.equal(video.muted, true);
|
|
|
|
controller.toggleMute();
|
|
assert.equal(video.muted, false);
|
|
|
|
video.controls = false;
|
|
controller.enableNativeControls();
|
|
assert.equal(video.controls, true);
|
|
});
|
|
|
|
test('MediaController toggles loop playback state', () => {
|
|
const { controller, video } = createController();
|
|
|
|
assert.equal(controller.isLooping(), false);
|
|
assert.equal(controller.toggleLoop(), true);
|
|
assert.equal(video.loop, true);
|
|
assert.equal(controller.isLooping(), true);
|
|
assert.equal(controller.toggleLoop(), false);
|
|
assert.equal(video.loop, false);
|
|
});
|
|
|
|
test('MediaController restarts looping video just before the ended boundary', () => {
|
|
const { controller, video } = createController({
|
|
video: createVideo({
|
|
currentTime: 119.9,
|
|
duration: 120,
|
|
loop: true,
|
|
paused: false
|
|
})
|
|
});
|
|
|
|
controller.handleTimeUpdate();
|
|
|
|
assert.equal(video.currentTime, 0);
|
|
assert.equal(video.playCount, 1);
|
|
});
|
|
|
|
test('MediaController leaves non-looping or paused video alone near the ended boundary', () => {
|
|
const { controller: nonLoopingController, video: nonLoopingVideo } = createController({
|
|
video: createVideo({
|
|
currentTime: 119.9,
|
|
duration: 120,
|
|
loop: false,
|
|
paused: false
|
|
})
|
|
});
|
|
|
|
nonLoopingController.handleTimeUpdate();
|
|
|
|
assert.equal(nonLoopingVideo.currentTime, 119.9);
|
|
assert.equal(nonLoopingVideo.playCount, 0);
|
|
|
|
const { controller: pausedController, video: pausedVideo } = createController({
|
|
video: createVideo({
|
|
currentTime: 119.9,
|
|
duration: 120,
|
|
loop: true,
|
|
paused: true
|
|
})
|
|
});
|
|
|
|
pausedController.handleTimeUpdate();
|
|
|
|
assert.equal(pausedVideo.currentTime, 119.9);
|
|
assert.equal(pausedVideo.playCount, 0);
|
|
});
|
|
|
|
test('MediaController resets video and play button to poster state', () => {
|
|
const playButton = { classList: createClassList(), disabled: true };
|
|
playButton.classList.add('hidden');
|
|
const { controller, video } = createController({ playButton });
|
|
|
|
controller.resetToOriginalState();
|
|
|
|
assert.equal(video.pauseCount, 1);
|
|
assert.equal(video.currentTime, 0);
|
|
assert.equal(video.controls, false);
|
|
assert.equal(video.loadCount, 1);
|
|
assert.equal(playButton.classList.contains('hidden'), false);
|
|
assert.equal(playButton.disabled, false);
|
|
});
|
|
|
|
test('MediaController restarts ended video before playing again', async () => {
|
|
let resumed = false;
|
|
const { controller, video } = createController({
|
|
is2DModeActive: () => true,
|
|
on2DPlaybackResume: () => {
|
|
resumed = true;
|
|
},
|
|
video: createVideo({ currentTime: 120, ended: true, paused: true })
|
|
});
|
|
|
|
controller.togglePlayPause();
|
|
await Promise.resolve();
|
|
|
|
assert.equal(video.currentTime, 0);
|
|
assert.equal(video.playCount, 1);
|
|
assert.equal(resumed, true);
|
|
|
|
const vrVideo = createVideo({ currentTime: 120, ended: true, paused: true });
|
|
const { controller: vrController } = createController({ video: vrVideo });
|
|
|
|
vrController.togglePlayPause();
|
|
await Promise.resolve();
|
|
|
|
assert.equal(vrVideo.currentTime, 0);
|
|
assert.equal(vrVideo.playCount, 1);
|
|
});
|
|
|
|
test('MediaController pauses when toggling playback while already playing', () => {
|
|
const { controller, video } = createController({
|
|
video: createVideo({ paused: false })
|
|
});
|
|
|
|
controller.togglePlayPause();
|
|
|
|
assert.equal(video.pauseCount, 1);
|
|
assert.equal(video.paused, true);
|
|
});
|
|
|
|
test('MediaController dispatches ended behavior for VR, 2D, and idle modes', () => {
|
|
const vrCalls = [];
|
|
const { controller } = createController({
|
|
video: createVideo({ paused: false })
|
|
});
|
|
|
|
controller.handleEnded({
|
|
isIn2DMode: () => false,
|
|
isInVr: () => true,
|
|
on2DEnded: () => vrCalls.push('2d'),
|
|
onVrEnded: () => vrCalls.push('vr'),
|
|
resetToOriginalState: () => vrCalls.push('reset')
|
|
});
|
|
assert.deepEqual(vrCalls, ['vr']);
|
|
|
|
const twoDCalls = [];
|
|
controller.handleEnded({
|
|
isIn2DMode: () => true,
|
|
isInVr: () => false,
|
|
on2DEnded: () => twoDCalls.push('2d'),
|
|
onVrEnded: () => twoDCalls.push('vr'),
|
|
resetToOriginalState: () => twoDCalls.push('reset')
|
|
});
|
|
assert.deepEqual(twoDCalls, ['2d']);
|
|
|
|
const idleCalls = [];
|
|
controller.handleEnded({
|
|
isIn2DMode: () => false,
|
|
isInVr: () => false,
|
|
on2DEnded: () => idleCalls.push('2d'),
|
|
onVrEnded: () => idleCalls.push('vr'),
|
|
resetToOriginalState: () => idleCalls.push('reset')
|
|
});
|
|
assert.deepEqual(idleCalls, ['reset']);
|
|
});
|