Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: don't flash default controls when using custom interactive player controls #33

Merged
merged 14 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
*
* - Class {@link InteractiveBridge} enables a Livery interactive layer element or page to communicate with
* the surrounding Livery Player
* - Element {@link LiveryBridgeLog} logs messages posted to specified bridge or the window
* - Types {@link Orientation} and {@link StreamPhase} are used throughout the package
* - Variable {@link version} specifies the version of this package
* - Class {@link AbstractPlayerBridge} is used by LiveryPlayer for the `PlayerBridge` implementation
* (in the past: {@link LiveryBridge})
* - Class {@link MockPlayerBridge} is a player bridge that returns mock data for testing
* - Element {@link LiveryBridgeLog} logs messages posted to specified bridge or the window
* - Element {@link LiveryBridgeMock} mocks a LiveryPlayer with an interactive child element or iframe for testing
* - Element {@link LiveryBridgeInteractive} is an interactive element that enables testing all interactive commands
* - Variable {@link version} specifies the version of this package
* - Schema types:
* {@link AuthClaims}, {@link Config}, {@link DisplayMode}, {@link Features}, {@link InteractivePlayerOptions},
* {@link Orientation}, {@link PlaybackDetails}, {@link PlaybackMode}, {@link PlaybackState}, {@link Qualities},
* {@link Quality}, {@link StreamPhase}, {@link UserFeedback} and {@link Volume}.
*
* **Note:** When using the UMD bundle, the exports can be found as properties of `livery` in the global namespace,
* e.g: `livery.version`.
Expand All @@ -37,6 +40,7 @@ export type {
Config,
DisplayMode,
Features,
InteractivePlayerOptions,
Orientation,
PlaybackDetails,
PlaybackMode,
Expand Down
27 changes: 19 additions & 8 deletions src/AbstractPlayerBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
import {
validateBoolean,
validateDisplayMode,
validateInteractivePlayerOptions,
validateNumber,
validateUserFeedback,
} from './util/schema';
Expand All @@ -42,7 +43,22 @@ export abstract class AbstractPlayerBridge extends LiveryBridge {
* @param tokenOrClaims - JWT token string or claims to authenticate with or undefined to logout
*/
authenticate(tokenOrClaims?: string | AuthClaims) {
return this.sendCommand<void>('authenticate', tokenOrClaims);
return this.sendCommand('authenticate', tokenOrClaims);
}

/**
* Returns promise of options from interactive layer for the player.
* Or default options if the interactive bridge doesn't support this/these yet.
*
* Note: In the future this could also pass options from player to the interactive layer.
*
* @deprecated In the next major version options passing should be integrated into the LiveryBridge handshake.
*/
options() {
return this.sendCommand('options').then(
(value) => validateInteractivePlayerOptions(value),
() => validateInteractivePlayerOptions(undefined),
);
}

/**
Expand All @@ -60,10 +76,10 @@ export abstract class AbstractPlayerBridge extends LiveryBridge {
* Returns promise of value returned by the interactive layer's custom command handler with matching `name` that is passed `arg`.
* Any `handler` `listener` calls will subsequently also be bridged to this `listener` callback.
*/
sendInteractiveCommand<T>(
sendInteractiveCommand(
name: string,
arg?: unknown,
listener?: (value: T) => void,
listener?: (value: unknown) => void,
) {
return this.sendCustomCommand(name, arg, listener);
}
Expand Down Expand Up @@ -122,9 +138,6 @@ export abstract class AbstractPlayerBridge extends LiveryBridge {
if (name === 'selectQuality') {
return this.selectQuality(validateNumber(arg));
}
if (name === 'setControlsDisabled') {
return this.setControlsDisabled(validateBoolean(arg));
}
if (name === 'setDisplay') {
return this.setDisplay(validateDisplayMode(arg));
}
Expand Down Expand Up @@ -278,8 +291,6 @@ export abstract class AbstractPlayerBridge extends LiveryBridge {

protected abstract selectQuality(index: number): void;

protected abstract setControlsDisabled(disabled: boolean): void;

protected abstract setDisplay(display: DisplayMode): void;

protected abstract setMuted(muted: boolean): void;
Expand Down
47 changes: 24 additions & 23 deletions src/InteractiveBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
validateConfig,
validateDisplayMode,
validateFeatures,
validateInteractivePlayerOptions,
validateNumberOrNan,
validateOrientation,
validatePlaybackDetails,
Expand Down Expand Up @@ -61,11 +62,15 @@ export class InteractiveBridge extends LiveryBridge {
* or with `window.parent` as target window and with specified string as origin.
*
* @param target - Player bridge or window origin to target
* @param options - Options
* @param options - Options for this bridge and {@link InteractivePlayerOptions}
*/
constructor(
target: AbstractPlayerBridge | string,
// !! This includes copies of all InteractivePlayerOptions from schema !!
// Unfortunately TypeDoc does not properly support just referencing `& InteractivePlayerOptions`
options: {
/** True if default player controls should be disabled to use custom controls instead, false otherwise. */
controlsDisabled?: boolean;
/**
* Handles authentication data coming from the player.
*
Expand Down Expand Up @@ -166,7 +171,7 @@ export class InteractiveBridge extends LiveryBridge {
* Pause playback.
*/
pause() {
return this.sendCommand<void>('pause');
return this.sendCommand('pause');
}

/**
Expand All @@ -177,7 +182,7 @@ export class InteractiveBridge extends LiveryBridge {
* Or if that also fails then {@link subscribePaused} will remain true.
*/
play() {
return this.sendCommand<void>('play');
return this.sendCommand('play');
}

/**
Expand Down Expand Up @@ -211,7 +216,7 @@ export class InteractiveBridge extends LiveryBridge {
* Reload player, e.g: to try to recover from an error.
*/
reload() {
return this.sendCommand<void>('reload');
return this.sendCommand('reload');
}

/**
Expand All @@ -225,7 +230,7 @@ export class InteractiveBridge extends LiveryBridge {
* @param position - Position in seconds since start of stream/VOD to seek to
*/
seek(position: number) {
return this.sendCommand<void>('seek', position);
return this.sendCommand('seek', position);
}

/**
Expand All @@ -234,7 +239,7 @@ export class InteractiveBridge extends LiveryBridge {
* @param index - Index of quality to select
*/
selectQuality(index: number) {
return this.sendCommand<void>('selectQuality', index);
return this.sendCommand('selectQuality', index);
}

/**
Expand All @@ -243,10 +248,10 @@ export class InteractiveBridge extends LiveryBridge {
*
* @deprecated Instead use {@link sendPlayerCommand}
*/
override sendCustomCommand<T>(
override sendCustomCommand(
name: string,
arg?: unknown,
listener?: (value: T) => void,
listener?: (value: unknown) => void,
) {
return super.sendCustomCommand(name, arg, listener);
}
Expand All @@ -259,23 +264,14 @@ export class InteractiveBridge extends LiveryBridge {
* @param arg - Optional argument to custom command to send
* @param listener - Optional listener function to be called with custom command handler listener call values
*/
sendPlayerCommand<T>(
sendPlayerCommand(
name: string,
arg?: unknown,
listener?: (value: T) => void,
listener?: (value: unknown) => void,
) {
return super.sendCustomCommand(name, arg, listener);
}

/**
* Change `disabled` to `true` to disable all default player controls and implement your own instead.
*
* @param disabled - True if default player controls should be disabled, false otherwise
*/
setControlsDisabled(disabled: boolean) {
return this.sendCommand<void>('setControlsDisabled', disabled);
}

/**
* Attempt to change display mode to specified value.
*
Expand All @@ -286,7 +282,7 @@ export class InteractiveBridge extends LiveryBridge {
* @param display - Display mode to attempt to change to
*/
setDisplay(display: DisplayMode) {
return this.sendCommand<void>('setDisplay', display);
return this.sendCommand('setDisplay', display);
}

/**
Expand All @@ -299,7 +295,7 @@ export class InteractiveBridge extends LiveryBridge {
* @param muted - Muted state to attempt to change to
*/
setMuted(muted: boolean) {
return this.sendCommand<void>('setMuted', muted);
return this.sendCommand('setMuted', muted);
}

/**
Expand All @@ -315,7 +311,7 @@ export class InteractiveBridge extends LiveryBridge {
* @param volume - Volume, between 0 and 1, to change to
*/
setVolume(volume: number) {
return this.sendCommand<void>('setVolume', volume);
return this.sendCommand('setVolume', volume);
}

/**
Expand All @@ -326,7 +322,7 @@ export class InteractiveBridge extends LiveryBridge {
* @param feedback - User feedback to submit
*/
submitUserFeedback(feedback: UserFeedback) {
return this.sendCommand<void>('submitUserFeedback', feedback);
return this.sendCommand('submitUserFeedback', feedback);
}

/**
Expand Down Expand Up @@ -546,6 +542,11 @@ export class InteractiveBridge extends LiveryBridge {
if (name === 'authenticate') {
return this.authenticate(validateAuth(arg));
}
/** @deprecated In the next major version options passing should be integrated into the LiveryBridge handshake. */
if (name === 'options') {
// Just return InteractivePlayerOptions, not handleAuth option
return validateInteractivePlayerOptions(this.options);
}
return super.handleCommand(name, arg, listener);
}

Expand Down
34 changes: 17 additions & 17 deletions src/LiveryBridge.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// We carefully work with unsafe message data within this class, so we will use `any` typed variables and arguments
/* eslint-disable @typescript-eslint/no-explicit-any */

import { isSemVerCompatible } from './util/semver';
import { uuid } from './util/uuid';

interface LiveryMessage extends Record<string, any> {
// TODO: Replace type validation code here by using Zod in schema?
// TODO: At next major release add support for options to handshake protocol and use that to replace options command

interface LiveryMessage extends Record<string, unknown> {
id: string;
isLivery: true;
sourceId: string;
Expand All @@ -18,7 +18,7 @@ interface HandshakeMessage extends LiveryMessage {

interface ResolveMessage extends LiveryMessage {
type: 'resolve';
value: any;
value: unknown;
}

interface RejectMessage extends LiveryMessage {
Expand All @@ -27,20 +27,20 @@ interface RejectMessage extends LiveryMessage {
}

interface CommandMessage extends LiveryMessage {
arg: any;
arg: unknown;
name: string;
type: 'command';
}

interface CustomCommandMessage extends LiveryMessage {
arg: any;
arg: unknown;
name: string;
type: 'customCommand';
}

interface EventMessage extends LiveryMessage {
type: 'event';
value: any;
value: unknown;
}

type Spy = (message: LiveryMessage) => void;
Expand Down Expand Up @@ -85,13 +85,13 @@ export class LiveryBridge {
string,
{
reject: (error: Error) => void;
resolve: (value: any) => void;
resolve: (value: unknown) => void;
}
>();

private handshakePromise: Promise<void>;
private handshakePromise: Promise<unknown>;

private listenerMap = new Map<string, (value: any) => void>();
private listenerMap = new Map<string, (value: unknown) => void>();

private sourceId = uuid();

Expand All @@ -117,7 +117,7 @@ export class LiveryBridge {
constructor(target?: LiveryBridge['target']) {
this.target = target;

this.handshakePromise = new Promise<void>((resolve, reject) => {
this.handshakePromise = new Promise<unknown>((resolve, reject) => {
this.deferredMap.set(this.sourceId, { resolve, reject });
});

Expand Down Expand Up @@ -258,16 +258,16 @@ export class LiveryBridge {
this.customCommandMap.set(name, handler);
}

protected sendCommand<T>(
protected sendCommand(
name: string,
arg?: unknown,
listener?: (value: T) => void,
listener?: (value: unknown) => void,
custom = false,
) {
return this.handshakePromise.then(() => {
const id = uuid();

const promise = new Promise<T>((resolve, reject) => {
const promise = new Promise<unknown>((resolve, reject) => {
this.deferredMap.set(id, { resolve, reject });
});

Expand All @@ -285,10 +285,10 @@ export class LiveryBridge {
* Returns promise of value returned by other side's custom command handler with matching `name` that is passed `arg`.
* Any `handler` `listener` calls will subsequently also be bridged to this `listener` callback.
*/
protected sendCustomCommand<T>(
protected sendCustomCommand(
name: string,
arg?: unknown,
listener?: (value: T) => void,
listener?: (value: unknown) => void,
) {
return this.sendCommand(name, arg, listener, true);
}
Expand Down
4 changes: 0 additions & 4 deletions src/MockPlayerBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,6 @@ export class MockPlayerBridge extends AbstractPlayerBridge {
this.qualitiesListeners.forEach((listener) => listener(this.qualities));
}

protected setControlsDisabled(disabled: boolean) {
this.controlsDisabled = disabled;
}

protected setDisplay(display: DisplayMode) {
if (display === this.display) {
return;
Expand Down
Loading
Loading