mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 10:06:15 -08:00
feat(playback): add picture-in-picture controls contract
This commit is contained in:
1 parent
7e06682f6e
commit
387d02d275
12 files changed
+50
-16
No files matched your search
@@ -198,6 +198,7 @@ describe('EmbeddedMpvControlsAdapter', () => {
|
||||
playbackSpeed: true,
|
||||
aspectRatio: true,
|
||||
recording: true,
|
||||
pictureInPicture: false,
|
||||
fullscreen: true,
|
||||
seriesNavigation: true,
|
||||
});
|
||||
@@ -222,6 +223,7 @@ describe('EmbeddedMpvControlsAdapter', () => {
|
||||
playbackSpeed: false,
|
||||
aspectRatio: false,
|
||||
recording: false,
|
||||
pictureInPicture: false,
|
||||
fullscreen: true,
|
||||
seriesNavigation: true,
|
||||
});
|
||||
@@ -263,6 +265,8 @@ describe('EmbeddedMpvControlsAdapter', () => {
|
||||
speedPresets: DEFAULT_SPEED_PRESETS,
|
||||
aspectRatio: '16:9',
|
||||
aspectPresets: DEFAULT_ASPECT_PRESETS,
|
||||
pictureInPictureActive: false,
|
||||
canPictureInPicture: false,
|
||||
canPreviousEpisode: true,
|
||||
canNextEpisode: false,
|
||||
});
|
||||
@@ -374,6 +378,7 @@ describe('EmbeddedMpvControlsAdapter', () => {
|
||||
it('delegates every non-recording shared-controls command', () => {
|
||||
configure();
|
||||
|
||||
expect(() => adapter.commands.togglePictureInPicture()).not.toThrow();
|
||||
adapter.commands.togglePlay();
|
||||
adapter.commands.seekTo(45);
|
||||
adapter.commands.seekBy(-10);
|
||||
|
||||
@@ -114,6 +114,7 @@ export class EmbeddedMpvControlsAdapter implements PlayerController {
|
||||
playbackSpeed: optionalCapabilities?.playbackSpeed ?? false,
|
||||
aspectRatio: optionalCapabilities?.aspectOverride ?? false,
|
||||
recording: optionalCapabilities?.recording ?? false,
|
||||
pictureInPicture: false,
|
||||
fullscreen: true,
|
||||
seriesNavigation: !isLive && context.seriesNavigation() !== null,
|
||||
};
|
||||
@@ -190,6 +191,8 @@ export class EmbeddedMpvControlsAdapter implements PlayerController {
|
||||
),
|
||||
transitionKey: this.recordingTransitionKey(),
|
||||
},
|
||||
pictureInPictureActive: false,
|
||||
canPictureInPicture: false,
|
||||
canPreviousEpisode:
|
||||
hasSeriesNavigation && seriesNavigation?.canPrevious === true,
|
||||
canNextEpisode:
|
||||
@@ -207,6 +210,7 @@ export class EmbeddedMpvControlsAdapter implements PlayerController {
|
||||
setPlaybackSpeed: (speed) => void this.controller.setSpeed(speed),
|
||||
setAspectRatio: (value) => void this.controller.setAspect(value),
|
||||
toggleRecording: () => void this.toggleRecording(),
|
||||
togglePictureInPicture: () => undefined,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
|
||||
@@ -21,6 +21,7 @@ describe('ControlsMenuSelection', () => {
|
||||
setPlaybackSpeed: jest.fn(),
|
||||
setAspectRatio: jest.fn(),
|
||||
toggleRecording: jest.fn(),
|
||||
togglePictureInPicture: jest.fn(),
|
||||
};
|
||||
menus = new ControlsMenuState();
|
||||
visibility = new ControlsVisibility(() => false);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
DEFAULT_PLAYER_CAPABILITIES,
|
||||
createEmptyControlsState,
|
||||
} from './player-controls-defaults';
|
||||
|
||||
describe('player-controls defaults', () => {
|
||||
it('defaults Picture-in-Picture to unsupported and inactive', () => {
|
||||
expect(DEFAULT_PLAYER_CAPABILITIES.pictureInPicture).toBe(false);
|
||||
expect(createEmptyControlsState()).toMatchObject({
|
||||
pictureInPictureActive: false,
|
||||
canPictureInPicture: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,7 @@ export const DEFAULT_PLAYER_CAPABILITIES: PlayerControlsCapabilities = {
|
||||
playbackSpeed: false,
|
||||
aspectRatio: false,
|
||||
recording: false,
|
||||
pictureInPicture: false,
|
||||
fullscreen: false,
|
||||
seriesNavigation: false,
|
||||
};
|
||||
@@ -51,6 +52,8 @@ export function createEmptyControlsState(): PlayerControlsState {
|
||||
aspectRatio: 'no',
|
||||
aspectPresets: DEFAULT_ASPECT_PRESETS,
|
||||
recording: { active: false, elapsedSeconds: 0, message: null },
|
||||
pictureInPictureActive: false,
|
||||
canPictureInPicture: false,
|
||||
canPreviousEpisode: false,
|
||||
canNextEpisode: false,
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ function createFakeController(): FakeController {
|
||||
setPlaybackSpeed: jest.fn(),
|
||||
setAspectRatio: jest.fn(),
|
||||
toggleRecording: jest.fn(),
|
||||
togglePictureInPicture: jest.fn(),
|
||||
};
|
||||
return {
|
||||
controller: { capabilities, state, commands },
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ function createFakeController() {
|
||||
setPlaybackSpeed: jest.fn(),
|
||||
setAspectRatio: jest.fn(),
|
||||
toggleRecording: jest.fn(),
|
||||
togglePictureInPicture: jest.fn(),
|
||||
};
|
||||
const controller: PlayerController = { capabilities, state, commands };
|
||||
return { controller, capabilities, state, commands };
|
||||
|
||||
@@ -28,6 +28,7 @@ function createFakeController() {
|
||||
setPlaybackSpeed: jest.fn(),
|
||||
setAspectRatio: jest.fn(),
|
||||
toggleRecording: jest.fn(),
|
||||
togglePictureInPicture: jest.fn(),
|
||||
};
|
||||
const controller: PlayerController = { capabilities, state, commands };
|
||||
return { controller, capabilities, state, commands };
|
||||
|
||||
+11
-16
@@ -30,6 +30,7 @@ function createFakeController() {
|
||||
setPlaybackSpeed: jest.fn(),
|
||||
setAspectRatio: jest.fn(),
|
||||
toggleRecording: jest.fn(),
|
||||
togglePictureInPicture: jest.fn(),
|
||||
};
|
||||
const controller: PlayerController = { capabilities, state, commands };
|
||||
return { controller, capabilities, state, commands };
|
||||
@@ -298,23 +299,17 @@ describe('PlayerControlsComponent surface, fullscreen and shortcuts', () => {
|
||||
});
|
||||
|
||||
describe('keyboard shortcuts', () => {
|
||||
it('does not consume or toggle playback while loading', () => {
|
||||
setState({ status: 'loading' });
|
||||
fixture.detectChanges();
|
||||
it.each(['loading', 'error'] as const)(
|
||||
'does not consume or toggle playback while status is %s',
|
||||
(status) => {
|
||||
setState({ status });
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(pressKey(' ')).toBe(false);
|
||||
expect(pressKey('k')).toBe(false);
|
||||
expect(fake.commands.togglePlay).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not consume or toggle playback after an error', () => {
|
||||
setState({ status: 'error' });
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(pressKey(' ')).toBe(false);
|
||||
expect(pressKey('k')).toBe(false);
|
||||
expect(fake.commands.togglePlay).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(pressKey(' ')).toBe(false);
|
||||
expect(pressKey('k')).toBe(false);
|
||||
expect(fake.commands.togglePlay).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('Escape closes an open menu', () => {
|
||||
component.toggleMenu('subtitle');
|
||||
|
||||
@@ -27,6 +27,7 @@ function createFakeController() {
|
||||
setPlaybackSpeed: jest.fn(),
|
||||
setAspectRatio: jest.fn(),
|
||||
toggleRecording: jest.fn(),
|
||||
togglePictureInPicture: jest.fn(),
|
||||
};
|
||||
const controller: PlayerController = {
|
||||
capabilities,
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface PlayerControlsCapabilities {
|
||||
playbackSpeed: boolean;
|
||||
aspectRatio: boolean;
|
||||
recording: boolean;
|
||||
pictureInPicture: boolean;
|
||||
fullscreen: boolean;
|
||||
seriesNavigation: boolean;
|
||||
}
|
||||
@@ -61,6 +62,8 @@ export interface PlayerControlsState {
|
||||
aspectRatio: string;
|
||||
aspectPresets: ReadonlyArray<PlayerPreset<string>>;
|
||||
recording: PlayerRecordingState;
|
||||
pictureInPictureActive: boolean;
|
||||
canPictureInPicture: boolean;
|
||||
canPreviousEpisode: boolean;
|
||||
canNextEpisode: boolean;
|
||||
}
|
||||
@@ -76,6 +79,7 @@ export interface PlayerControlsCommands {
|
||||
setPlaybackSpeed(speed: number): void;
|
||||
setAspectRatio(value: string): void;
|
||||
toggleRecording(): void;
|
||||
togglePictureInPicture(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,6 +115,7 @@ export class WebVideoControlsAdapter implements PlayerController {
|
||||
subtitles: hasSubtitles,
|
||||
aspectRatio: false,
|
||||
recording: false,
|
||||
pictureInPicture: false,
|
||||
seriesNavigation: !isLive && this.seriesNavigation() !== null,
|
||||
};
|
||||
});
|
||||
@@ -151,6 +152,8 @@ export class WebVideoControlsAdapter implements PlayerController {
|
||||
aspectRatio: 'no',
|
||||
aspectPresets: DEFAULT_ASPECT_PRESETS,
|
||||
recording: { active: false, elapsedSeconds: 0, message: null },
|
||||
pictureInPictureActive: false,
|
||||
canPictureInPicture: false,
|
||||
canPreviousEpisode:
|
||||
seriesNavCapable && seriesNav?.canPrevious === true,
|
||||
canNextEpisode: seriesNavCapable && seriesNav?.canNext === true,
|
||||
@@ -170,6 +173,7 @@ export class WebVideoControlsAdapter implements PlayerController {
|
||||
setPlaybackSpeed: (speed) => this.applySpeed(speed),
|
||||
setAspectRatio: () => undefined,
|
||||
toggleRecording: () => undefined,
|
||||
togglePictureInPicture: () => undefined,
|
||||
};
|
||||
|
||||
/** Binds to a `<video>` element and starts maintaining the state signal. */
|
||||
|
||||
Reference in new issue
Block a user