ogplayer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +7 -0
- package/README.md +129 -0
- package/dist/ads/freewheel-provider.d.ts +159 -0
- package/dist/ads/freewheel.d.ts +52 -0
- package/dist/ads/ima.d.ts +157 -0
- package/dist/ads/provider.d.ts +56 -0
- package/dist/ads/types.d.ts +42 -0
- package/dist/core/errors.d.ts +14 -0
- package/dist/core/fairplay.d.ts +24 -0
- package/dist/core/license.d.ts +26 -0
- package/dist/core/player.d.ts +182 -0
- package/dist/core/thumbnails.d.ts +26 -0
- package/dist/core/types.d.ts +204 -0
- package/dist/index.d.ts +17 -0
- package/dist/ogplayer.global.js +285 -0
- package/dist/ogplayer.js +285 -0
- package/dist/ui/icons.d.ts +14 -0
- package/dist/ui/og-player.d.ts +167 -0
- package/dist/ui/tokens.d.ts +51 -0
- package/package.json +39 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# OGPlayer Commercial License
|
|
2
|
+
|
|
3
|
+
The OGPlayer SDK is commercial software by Inverse DOO — **free to
|
|
4
|
+
evaluate** (the player renders an OGPlayer watermark); **production use
|
|
5
|
+
requires a license key**.
|
|
6
|
+
|
|
7
|
+
Full terms: https://ogplayer.tv/terms/ · Licensing: sales@ogplayer.tv
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# OGPlayer — web SDK
|
|
2
|
+
|
|
3
|
+
One video player API across **Android, iOS and the web**. This package is the
|
|
4
|
+
web implementation: the same model types, listener callbacks, error codes and
|
|
5
|
+
UI behavior as the Android SDK (the reference platform), built on web
|
|
6
|
+
standards.
|
|
7
|
+
|
|
8
|
+
> OGPlayer is a product of Inverse DOO.
|
|
9
|
+
|
|
10
|
+
## How the web version works (for non-web people)
|
|
11
|
+
|
|
12
|
+
- **Language:** TypeScript — typed like Kotlin/Swift, compiles to JavaScript.
|
|
13
|
+
- **Playback:** the browser's `<video>` element plays the media. HLS streams
|
|
14
|
+
are fed to it through [hls.js](https://github.com/video-dev/hls.js) using
|
|
15
|
+
Media Source Extensions (the web's ExoPlayer, used by YouTube/Twitch-class
|
|
16
|
+
players); Safari plays HLS natively so hls.js steps aside there.
|
|
17
|
+
Progressive MP4 plays directly. This is the exact architecture JW Player,
|
|
18
|
+
Video.js and Mux use.
|
|
19
|
+
- **UI:** a **custom element** `<og-player>` — the web-standard way to ship a
|
|
20
|
+
reusable component that works identically in plain HTML, React, Vue and
|
|
21
|
+
Angular, with all styles isolated in a shadow DOM so host-page CSS can't
|
|
22
|
+
break the player (and vice versa). The web parallel of "Compose-only /
|
|
23
|
+
SwiftUI-only": one modern UI technology, no legacy widgets.
|
|
24
|
+
- **License:** the same `OGP1.…` offline keys as Android/iOS, verified with
|
|
25
|
+
the browser's built-in WebCrypto against the same P-256 public key — one
|
|
26
|
+
license unlocks all three platforms. On the web, `apps` patterns bind to
|
|
27
|
+
the page **hostname**.
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<script type="module">
|
|
33
|
+
import { OGPlayer } from "ogplayer";
|
|
34
|
+
|
|
35
|
+
const player = new OGPlayer({ licenseKey: "OGP1...." });
|
|
36
|
+
document.querySelector("og-player").player = player;
|
|
37
|
+
|
|
38
|
+
player.addListener({
|
|
39
|
+
onStateChanged: (s) => console.log(s),
|
|
40
|
+
onProgress: (positionMs, bufferedMs, durationMs) => {},
|
|
41
|
+
});
|
|
42
|
+
player.load({
|
|
43
|
+
url: "https://example.com/stream.m3u8",
|
|
44
|
+
title: "My movie",
|
|
45
|
+
contentRatings: [{ age: "SIXTEEN" }, { descriptor: "FEAR" }],
|
|
46
|
+
sideloadedSubtitles: [{ url: "/subs/en.vtt", language: "en", label: "English", isDefault: true }],
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<og-player style="width:100%;aspect-ratio:16/9"></og-player>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Overlays (watermarks, logos) use the nine named slots, mirroring
|
|
54
|
+
Android/iOS `OverlaySlot` — including the clearance choreography around the
|
|
55
|
+
controls and rating icons:
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<og-player>
|
|
59
|
+
<img slot="top-end" src="/logo.png" width="90">
|
|
60
|
+
</og-player>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Custom action icons (max 8, inline in the top-end control row, hide with the
|
|
64
|
+
controls — same contract as Android/iOS):
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
el.config = {
|
|
68
|
+
customActions: [
|
|
69
|
+
{ svg: shareIconSvg, accessibilityLabel: "Share", onClick: () => share() },
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## How it ships
|
|
75
|
+
|
|
76
|
+
1. **npm** (the web's Maven Central): `npm install ogplayer` — ESM module
|
|
77
|
+
with TypeScript types, hls.js pulled in as a dependency. Publishing is one
|
|
78
|
+
`npm publish` from CI; scoped to the `@ogplayer` org.
|
|
79
|
+
2. **CDN script tag** for no-build websites: `dist/ogplayer.global.js` is a
|
|
80
|
+
single self-contained file (hls.js included) exposing `window.OGPlayerSDK`.
|
|
81
|
+
Hosted on our own domain (e.g. `cdn.ogplayer.tv`) or jsDelivr once public.
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<script src="https://cdn.ogplayer.tv/0.1.0/ogplayer.global.js"></script>
|
|
85
|
+
<script>
|
|
86
|
+
const player = new OGPlayerSDK.OGPlayer();
|
|
87
|
+
document.querySelector("og-player").player = player;
|
|
88
|
+
player.load({ url: "…" });
|
|
89
|
+
</script>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Browser support
|
|
93
|
+
|
|
94
|
+
Evergreen Chrome / Edge / Firefox / Safari 16+ (desktop & mobile). Every API
|
|
95
|
+
used (MSE, custom elements, shadow DOM, WebCrypto, Fullscreen) has been
|
|
96
|
+
baseline for years; no polyfills.
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
npm install
|
|
102
|
+
npm run build # dist/ogplayer.js (ESM) + dist/ogplayer.global.js (CDN) + types
|
|
103
|
+
npm test # node --test (license crypto, matchers)
|
|
104
|
+
npm run dev # rebuild + serve demos at http://localhost:8123/demo/
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`scripts/smoke.mjs` runs the demo page in headless Chromium (playwright)
|
|
108
|
+
and asserts the engine reaches READY with zero page errors.
|
|
109
|
+
|
|
110
|
+
## Feature parity vs Android (reference)
|
|
111
|
+
|
|
112
|
+
| Feature | Android | iOS | Web |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| Transport / state / listeners | ✅ | ✅ | ✅ same names |
|
|
115
|
+
| Analytics events | ✅ | ✅ | ✅ same shapes |
|
|
116
|
+
| Error taxonomy | ✅ | ✅ | ✅ same codes |
|
|
117
|
+
| HLS + ABR qualities | ✅ | ✅ | ✅ (hls.js/native) |
|
|
118
|
+
| DASH | ✅ | — | ⏳ later (dash.js/Shaka) |
|
|
119
|
+
| Live & DVR | ✅ | ✅ | ✅ |
|
|
120
|
+
| Embedded + sideloaded subtitles | ✅ | ✅ | ✅ (native cue rendering) |
|
|
121
|
+
| Audio tracks | ✅ | ✅ | ✅ |
|
|
122
|
+
| Offline license (shared keypair) | ✅ | ✅ | ✅ WebCrypto |
|
|
123
|
+
| Controls chrome (tokens) | ✅ | ✅ | ✅ same tokens/icons |
|
|
124
|
+
| Title / NICAM ratings / watermark slots | ✅ | ✅ | ✅ incl. reflow choreography |
|
|
125
|
+
| Custom action icons (≤8) | ✅ | ✅ | ✅ |
|
|
126
|
+
| Ads (IMA / FreeWheel) | ✅ | ✅ | ⏳ SPI defined, provider later |
|
|
127
|
+
| DRM | Widevine/PlayReady | FairPlay | ⏳ EME later |
|
|
128
|
+
| Casting | Chromecast module | AirPlay | ⏳ Remote Playback API later |
|
|
129
|
+
| Trick-play thumbnails | ✅ | ✅ | ⏳ |
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { AdBreakConfig } from "./types.js";
|
|
2
|
+
import type { AdsProvider, AdsProviderCallbacks } from "./provider.js";
|
|
3
|
+
/**
|
|
4
|
+
* Native FreeWheel (MRM) implementation of the OGPlayer ads SPI — the web
|
|
5
|
+
* counterpart of Android's `FreewheelAdsProvider` (whose `FreewheelSession`
|
|
6
|
+
* this mirrors: request → slot schedule → preroll/midroll/catch-up/postroll,
|
|
7
|
+
* a watchdog that abandons a slot that never renders, exactly-once content
|
|
8
|
+
* resume per gate).
|
|
9
|
+
*
|
|
10
|
+
* FreeWheel's HTML5 SDK (`AdManager.js`) is **customer-licensed** and is
|
|
11
|
+
* never bundled: it is loaded at runtime from FreeWheel's CDN (the same way
|
|
12
|
+
* FW customers' own sites load it), overridable via `adManagerUrl`. The
|
|
13
|
+
* alternative integration — FW server as a VMAP tag through IMA — needs no
|
|
14
|
+
* FW SDK at all: pass the same `FreewheelConfig` to `ImaAdsProvider`.
|
|
15
|
+
*/
|
|
16
|
+
interface FwSlot {
|
|
17
|
+
play(): void;
|
|
18
|
+
stop?(): void;
|
|
19
|
+
pause?(): void;
|
|
20
|
+
resume?(): void;
|
|
21
|
+
getTimePosition(): number;
|
|
22
|
+
getTimePositionClass(): string;
|
|
23
|
+
getCustomId(): string;
|
|
24
|
+
getAdInstances?(): FwAdInstance[];
|
|
25
|
+
getPlayheadTime?(): number;
|
|
26
|
+
getTotalDuration?(): number;
|
|
27
|
+
}
|
|
28
|
+
interface FwAdInstance {
|
|
29
|
+
getAdId?(): number | string;
|
|
30
|
+
getDuration?(): number;
|
|
31
|
+
getActiveCreativeRendition?(): {
|
|
32
|
+
getDuration?(): number;
|
|
33
|
+
} | null;
|
|
34
|
+
getRendererController?(): {
|
|
35
|
+
skipCurrentAd?(): void;
|
|
36
|
+
} | null;
|
|
37
|
+
}
|
|
38
|
+
interface FwEvent {
|
|
39
|
+
success?: boolean;
|
|
40
|
+
slot?: FwSlot;
|
|
41
|
+
adInstance?: FwAdInstance;
|
|
42
|
+
errorInfo?: unknown;
|
|
43
|
+
[k: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
interface FwContext {
|
|
46
|
+
setProfile(profile: string): void;
|
|
47
|
+
setVideoAsset(id: string, durationSec: number, networkId?: number): void;
|
|
48
|
+
setSiteSection(id: string, networkId?: number): void;
|
|
49
|
+
addKeyValue(key: string, value: string): void;
|
|
50
|
+
setParameter(name: string, value: string, level: unknown): void;
|
|
51
|
+
registerVideoDisplayBase(elementId: string): void;
|
|
52
|
+
setContentVideoElement?(el: HTMLVideoElement): void;
|
|
53
|
+
addEventListener(type: string, cb: (e: FwEvent) => void): void;
|
|
54
|
+
removeEventListener?(type: string, cb: (e: FwEvent) => void): void;
|
|
55
|
+
submitRequest(): void;
|
|
56
|
+
getTemporalSlots(): FwSlot[];
|
|
57
|
+
setVideoState?(state: unknown): void;
|
|
58
|
+
dispose?(): void;
|
|
59
|
+
}
|
|
60
|
+
interface FwSdkNamespace {
|
|
61
|
+
AdManager: new () => {
|
|
62
|
+
setNetwork(id: number): void;
|
|
63
|
+
setServer(url: string): void;
|
|
64
|
+
newContext(): FwContext;
|
|
65
|
+
};
|
|
66
|
+
EVENT_REQUEST_COMPLETE: string;
|
|
67
|
+
EVENT_SLOT_STARTED: string;
|
|
68
|
+
EVENT_SLOT_ENDED: string;
|
|
69
|
+
EVENT_AD_IMPRESSION: string;
|
|
70
|
+
EVENT_AD_IMPRESSION_END?: string;
|
|
71
|
+
EVENT_AD_PAUSE?: string;
|
|
72
|
+
EVENT_AD_RESUME?: string;
|
|
73
|
+
EVENT_ERROR?: string;
|
|
74
|
+
TIME_POSITION_CLASS_PREROLL: string;
|
|
75
|
+
TIME_POSITION_CLASS_MIDROLL: string;
|
|
76
|
+
TIME_POSITION_CLASS_POSTROLL: string;
|
|
77
|
+
TIME_POSITION_CLASS_OVERLAY?: string;
|
|
78
|
+
PARAMETER_LEVEL_GLOBAL: unknown;
|
|
79
|
+
VIDEO_STATE_PLAYING?: unknown;
|
|
80
|
+
VIDEO_STATE_PAUSED?: unknown;
|
|
81
|
+
VIDEO_STATE_COMPLETED?: unknown;
|
|
82
|
+
}
|
|
83
|
+
declare global {
|
|
84
|
+
interface Window {
|
|
85
|
+
tv?: {
|
|
86
|
+
freewheel?: {
|
|
87
|
+
SDK?: FwSdkNamespace;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Options for {@link FreewheelAdsProvider}. */
|
|
93
|
+
export interface FreewheelAdsProviderOptions {
|
|
94
|
+
/** Where to load FreeWheel's AdManager.js from; defaults to FW's CDN
|
|
95
|
+
* (`libs/adm/7.5.0`). Point it at the version your network is on. */
|
|
96
|
+
adManagerUrl?: string;
|
|
97
|
+
}
|
|
98
|
+
export declare class FreewheelAdsProvider implements AdsProvider {
|
|
99
|
+
private readonly options;
|
|
100
|
+
constructor(options?: FreewheelAdsProviderOptions);
|
|
101
|
+
/** FW's HTML5 VideoRenderer plays ads IN the content element by design
|
|
102
|
+
* (`this._adVideo = getContentVideoElement()` in AdManager.js) — the
|
|
103
|
+
* engine releases the element around breaks and restores content after. */
|
|
104
|
+
readonly usesContentVideoElement = true;
|
|
105
|
+
private video;
|
|
106
|
+
private container;
|
|
107
|
+
private callbacks;
|
|
108
|
+
private sdk;
|
|
109
|
+
private context;
|
|
110
|
+
private cfg;
|
|
111
|
+
private generation;
|
|
112
|
+
private destroyed;
|
|
113
|
+
private prerollSlots;
|
|
114
|
+
private midrollSlots;
|
|
115
|
+
private postrollSlots;
|
|
116
|
+
private catchUpQueue;
|
|
117
|
+
private flow;
|
|
118
|
+
private contentGateOpened;
|
|
119
|
+
private pendingStart;
|
|
120
|
+
private currentSlot;
|
|
121
|
+
private currentBreakType;
|
|
122
|
+
private currentAd;
|
|
123
|
+
private currentInstance;
|
|
124
|
+
private adIndexInSlot;
|
|
125
|
+
private totalAdsInSlot;
|
|
126
|
+
private requestTimer;
|
|
127
|
+
private watchdogTimer;
|
|
128
|
+
private progressTimer;
|
|
129
|
+
private lastContentPosMs;
|
|
130
|
+
private videoListeners;
|
|
131
|
+
attach(video: HTMLVideoElement, adContainer: HTMLElement, callbacks: AdsProviderCallbacks): void;
|
|
132
|
+
requestAds(config: AdBreakConfig, autostart: boolean): void;
|
|
133
|
+
private submitRequest;
|
|
134
|
+
private onRequestComplete;
|
|
135
|
+
private publishCuePoints;
|
|
136
|
+
private playNextPreroll;
|
|
137
|
+
private openContentGateOnce;
|
|
138
|
+
private playSlot;
|
|
139
|
+
private onSlotEnded;
|
|
140
|
+
private onAdImpression;
|
|
141
|
+
private bindContentWatchers;
|
|
142
|
+
private unbindContentWatchers;
|
|
143
|
+
contentDidComplete(): void;
|
|
144
|
+
private playNextPostroll;
|
|
145
|
+
private armWatchdog;
|
|
146
|
+
private cancelWatchdog;
|
|
147
|
+
private abandonSlot;
|
|
148
|
+
private startProgressTicker;
|
|
149
|
+
private stopProgressTicker;
|
|
150
|
+
startPendingBreak(): boolean;
|
|
151
|
+
pause(): void;
|
|
152
|
+
resume(): void;
|
|
153
|
+
skip(): void;
|
|
154
|
+
click(): void;
|
|
155
|
+
resize(): void;
|
|
156
|
+
private fail;
|
|
157
|
+
destroy(): void;
|
|
158
|
+
}
|
|
159
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AdBreakConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* FreeWheel ad configuration — the web counterpart of the Android/iOS
|
|
4
|
+
* `FreewheelConfig`, field-for-field. Pure data: no FreeWheel SDK
|
|
5
|
+
* dependency.
|
|
6
|
+
*
|
|
7
|
+
* Two integration options, mirroring the mobile SDKs:
|
|
8
|
+
* (a) **FreeWheel as VMAP through IMA** — `freewheelVmapTagUrl(config)`
|
|
9
|
+
* builds the network's `/ad/g/1` VMAP request; play it with
|
|
10
|
+
* `ImaAdsProvider` via `adBreaks: { adTagUri }`. Works today.
|
|
11
|
+
* (b) A native FreeWheel HTML5 provider (their customer-licensed
|
|
12
|
+
* AdManager.js) — ships later as an adapter, like the iOS one.
|
|
13
|
+
*
|
|
14
|
+
* Consent and identity parameters (`_fw_gdpr`, `_fw_gdpr_consent`, device
|
|
15
|
+
* ids…) are the HOST's responsibility via `globalParameters` — the SDK
|
|
16
|
+
* never fabricates consent; it adds only the `pvrn`/`vprn` randomizers.
|
|
17
|
+
*/
|
|
18
|
+
export interface FreewheelConfig extends AdBreakConfig {
|
|
19
|
+
/** FreeWheel ad server base URL, e.g. `https://<your-network>.v.fwmrm.net`. */
|
|
20
|
+
serverUrl: string;
|
|
21
|
+
networkId: number;
|
|
22
|
+
/** FreeWheel profile, e.g. `<networkId>:<your_profile>`. */
|
|
23
|
+
profile: string;
|
|
24
|
+
siteSectionId: string;
|
|
25
|
+
videoAssetId: string;
|
|
26
|
+
/** Content duration in ms — explicit, never inferred. */
|
|
27
|
+
videoDurationMs: number;
|
|
28
|
+
/** GLOBAL-level request parameters, host-supplied verbatim. NOTE:
|
|
29
|
+
* FreeWheel's `flag` list is space-separated — pass real spaces
|
|
30
|
+
* (" play sltp …"); they encode to the `+` signs FreeWheel expects. */
|
|
31
|
+
globalParameters?: Record<string, string>;
|
|
32
|
+
/** Ad request timeout, default 5000. */
|
|
33
|
+
requestTimeoutMs?: number;
|
|
34
|
+
/** How close content time must get to a midroll cue, default 1000. */
|
|
35
|
+
midrollToleranceMs?: number;
|
|
36
|
+
/** Seeks shorter than this never trigger midroll catch-up, default 2000. */
|
|
37
|
+
seekThresholdMs?: number;
|
|
38
|
+
/** What plays after seeking across midroll cues, default "MOST_RECENT". */
|
|
39
|
+
seekCatchUpPolicy?: "NONE" | "MOST_RECENT" | "ALL_MOST_RECENT_FIRST";
|
|
40
|
+
/** AdInfo duration when FW doesn't expose one, default 15000. */
|
|
41
|
+
fallbackAdDurationMs?: number;
|
|
42
|
+
/** Renderer start watchdog before a slot is abandoned, default 10000. */
|
|
43
|
+
adStartTimeoutMs?: number;
|
|
44
|
+
}
|
|
45
|
+
/** True when an `AdBreakConfig` is a {@link FreewheelConfig}. */
|
|
46
|
+
export declare function isFreewheelConfig(c: AdBreakConfig): c is FreewheelConfig;
|
|
47
|
+
/**
|
|
48
|
+
* Build the VMAP tag URL for a FreeWheel network — integration option (a):
|
|
49
|
+
* feed the result to the IMA provider as a plain ad tag. Matches the request
|
|
50
|
+
* shape of the Android `asVmapTag` reference implementation.
|
|
51
|
+
*/
|
|
52
|
+
export declare function freewheelVmapTagUrl(cfg: FreewheelConfig): string;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { AdBreakConfig } from "./types.js";
|
|
2
|
+
import type { AdsProvider, AdsProviderCallbacks } from "./provider.js";
|
|
3
|
+
/**
|
|
4
|
+
* Google IMA (HTML5) implementation of the OGPlayer ads SPI — the web
|
|
5
|
+
* counterpart of Android's `ImaAdsProvider` / iOS's `IMAAdsProvider`.
|
|
6
|
+
* Client-side VAST/VMAP: IMA plays the ad in its own layer over the paused
|
|
7
|
+
* content element, then hands playback back.
|
|
8
|
+
*
|
|
9
|
+
* The IMA SDK script is loaded on demand from Google's servers (required by
|
|
10
|
+
* its terms — it cannot be bundled). As on the other platforms, IMA renders
|
|
11
|
+
* its own skip / clickthrough UI; the SDK draws the yellow bar and AD chip.
|
|
12
|
+
*/
|
|
13
|
+
interface ImaAd {
|
|
14
|
+
getAdId(): string;
|
|
15
|
+
getDuration(): number;
|
|
16
|
+
getAdPodInfo(): {
|
|
17
|
+
getAdPosition(): number;
|
|
18
|
+
getTotalAds(): number;
|
|
19
|
+
getPodIndex(): number;
|
|
20
|
+
};
|
|
21
|
+
getSkipTimeOffset(): number;
|
|
22
|
+
}
|
|
23
|
+
interface ImaAdEvent {
|
|
24
|
+
type: string;
|
|
25
|
+
getAd(): ImaAd | null;
|
|
26
|
+
getAdData(): {
|
|
27
|
+
currentTime?: number;
|
|
28
|
+
duration?: number;
|
|
29
|
+
} | null;
|
|
30
|
+
}
|
|
31
|
+
interface ImaAdError {
|
|
32
|
+
getErrorCode(): number;
|
|
33
|
+
getMessage(): string;
|
|
34
|
+
}
|
|
35
|
+
interface ImaAdsManager {
|
|
36
|
+
init(w: number, h: number, viewMode: unknown): void;
|
|
37
|
+
start(): void;
|
|
38
|
+
pause(): void;
|
|
39
|
+
resume(): void;
|
|
40
|
+
skip(): void;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
resize(w: number, h: number, viewMode: unknown): void;
|
|
43
|
+
getCuePoints(): number[];
|
|
44
|
+
addEventListener(type: string, cb: (e: ImaAdEvent) => void): void;
|
|
45
|
+
}
|
|
46
|
+
interface ImaNamespace {
|
|
47
|
+
AdDisplayContainer: new (el: HTMLElement, video: HTMLVideoElement) => {
|
|
48
|
+
initialize(): void;
|
|
49
|
+
destroy(): void;
|
|
50
|
+
};
|
|
51
|
+
AdsLoader: new (adc: unknown) => {
|
|
52
|
+
requestAds(req: unknown): void;
|
|
53
|
+
contentComplete(): void;
|
|
54
|
+
destroy(): void;
|
|
55
|
+
addEventListener(type: string, cb: (e: {
|
|
56
|
+
getAdsManager(video: HTMLVideoElement, settings: unknown): ImaAdsManager;
|
|
57
|
+
getError?(): ImaAdError;
|
|
58
|
+
}) => void): void;
|
|
59
|
+
};
|
|
60
|
+
AdsRequest: new () => {
|
|
61
|
+
adTagUrl: string;
|
|
62
|
+
linearAdSlotWidth: number;
|
|
63
|
+
linearAdSlotHeight: number;
|
|
64
|
+
nonLinearAdSlotWidth: number;
|
|
65
|
+
nonLinearAdSlotHeight: number;
|
|
66
|
+
setAdWillAutoPlay(v: boolean): void;
|
|
67
|
+
setAdWillPlayMuted(v: boolean): void;
|
|
68
|
+
};
|
|
69
|
+
AdsRenderingSettings: new () => {
|
|
70
|
+
uiElements: string[];
|
|
71
|
+
restoreCustomPlaybackStateOnAdBreakComplete: boolean;
|
|
72
|
+
};
|
|
73
|
+
AdsManagerLoadedEvent: {
|
|
74
|
+
Type: {
|
|
75
|
+
ADS_MANAGER_LOADED: string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
AdErrorEvent: {
|
|
79
|
+
Type: {
|
|
80
|
+
AD_ERROR: string;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
AdEvent: {
|
|
84
|
+
Type: Record<string, string>;
|
|
85
|
+
};
|
|
86
|
+
ViewMode: {
|
|
87
|
+
NORMAL: unknown;
|
|
88
|
+
FULLSCREEN: unknown;
|
|
89
|
+
};
|
|
90
|
+
ImaSdkSettings: {
|
|
91
|
+
VpaidMode: {
|
|
92
|
+
DISABLED: unknown;
|
|
93
|
+
ENABLED: unknown;
|
|
94
|
+
INSECURE: unknown;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
settings: {
|
|
98
|
+
setVpaidMode(mode: unknown): void;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
declare global {
|
|
102
|
+
interface Window {
|
|
103
|
+
google?: {
|
|
104
|
+
ima?: ImaNamespace;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Options for {@link ImaAdsProvider}. */
|
|
109
|
+
export interface ImaAdsProviderOptions {
|
|
110
|
+
/**
|
|
111
|
+
* How IMA runs VPAID (interactive JavaScript) creatives:
|
|
112
|
+
* - `"enabled"` (default) — VPAID in a secure cross-origin iframe.
|
|
113
|
+
* - `"insecure"` — VPAID in a friendly iframe. Some ad servers (e.g.
|
|
114
|
+
* FreeWheel networks) traffic VPAID creatives that stall in the secure
|
|
115
|
+
* sandbox, especially on plain-http origins; insecure mode lets them run.
|
|
116
|
+
* - `"disabled"` — VPAID creatives error out and are skipped.
|
|
117
|
+
*/
|
|
118
|
+
vpaidMode?: "enabled" | "insecure" | "disabled";
|
|
119
|
+
}
|
|
120
|
+
export declare class ImaAdsProvider implements AdsProvider {
|
|
121
|
+
private readonly options;
|
|
122
|
+
constructor(options?: ImaAdsProviderOptions);
|
|
123
|
+
private video;
|
|
124
|
+
private container;
|
|
125
|
+
private callbacks;
|
|
126
|
+
private ima;
|
|
127
|
+
private adDisplayContainer;
|
|
128
|
+
private adsLoader;
|
|
129
|
+
private adsManager;
|
|
130
|
+
private lastAd;
|
|
131
|
+
private lastBreakType;
|
|
132
|
+
private tagUrl;
|
|
133
|
+
private autostart;
|
|
134
|
+
private started;
|
|
135
|
+
private destroyed;
|
|
136
|
+
private requestGeneration;
|
|
137
|
+
private verdictTimer;
|
|
138
|
+
private gotVerdict;
|
|
139
|
+
attach(video: HTMLVideoElement, adContainer: HTMLElement, callbacks: AdsProviderCallbacks): void;
|
|
140
|
+
requestAds(config: AdBreakConfig, autostart: boolean): void;
|
|
141
|
+
private settleVerdict;
|
|
142
|
+
private startSession;
|
|
143
|
+
private bindManager;
|
|
144
|
+
/** After any ad failure, verify reachability — a confirmed blocker is
|
|
145
|
+
* reported as the dedicated 902 so the UI can show its notice. */
|
|
146
|
+
private adBlockFlagged;
|
|
147
|
+
private maybeFlagAdBlock;
|
|
148
|
+
startPendingBreak(): boolean;
|
|
149
|
+
contentDidComplete(): void;
|
|
150
|
+
pause(): void;
|
|
151
|
+
resume(): void;
|
|
152
|
+
skip(): void;
|
|
153
|
+
click(): void;
|
|
154
|
+
resize(width: number, height: number, fullscreen: boolean): void;
|
|
155
|
+
destroy(): void;
|
|
156
|
+
}
|
|
157
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AdBreakConfig, AdBreakType, AdInfo, OGAdError } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Events an `AdsProvider` reports back to the player — the web counterpart
|
|
4
|
+
* of the Android/iOS `AdsProviderCallbacks`. Implemented by `OGPlayer`. In
|
|
5
|
+
* addition to the ad lifecycle, the provider drives content pause/resume
|
|
6
|
+
* (client-side ads play while the content element is paused).
|
|
7
|
+
*/
|
|
8
|
+
export interface AdsProviderCallbacks {
|
|
9
|
+
onAdBreakStarted(breakType: AdBreakType, totalAds: number): void;
|
|
10
|
+
onAdStarted(ad: AdInfo): void;
|
|
11
|
+
onAdSkipped(ad: AdInfo): void;
|
|
12
|
+
onAdCompleted(ad: AdInfo): void;
|
|
13
|
+
onAdBreakCompleted(breakType: AdBreakType): void;
|
|
14
|
+
onAdPaused(ad: AdInfo): void;
|
|
15
|
+
onAdResumed(ad: AdInfo): void;
|
|
16
|
+
onAdProgress(ad: AdInfo, positionMs: number, durationMs: number): void;
|
|
17
|
+
onAdError(error: OGAdError): void;
|
|
18
|
+
/** VMAP cue-point times (seconds into content; negative = postroll). */
|
|
19
|
+
onAdCuePoints(cuePointsSeconds: number[]): void;
|
|
20
|
+
/** The whole ad schedule for this session finished (no breaks remain). */
|
|
21
|
+
onAllAdsCompleted?(): void;
|
|
22
|
+
/** A break is about to play — pause and hide the content. */
|
|
23
|
+
onContentPauseRequested(): void;
|
|
24
|
+
/** The break finished — resume content. */
|
|
25
|
+
onContentResumeRequested(): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* SPI for a client-side ads integration (Google IMA HTML5, future slot
|
|
29
|
+
* providers) — the web mirror of the mobile `AdsProvider` SPI. Opt-in: apps
|
|
30
|
+
* that never touch ads load none of this.
|
|
31
|
+
*/
|
|
32
|
+
export interface AdsProvider {
|
|
33
|
+
/** True when the provider plays its ads IN the content video element
|
|
34
|
+
* (FreeWheel's HTML5 SDK design — it swaps the src and the player
|
|
35
|
+
* restores content afterwards). The engine then releases the media
|
|
36
|
+
* element around each break and reloads content at the saved position
|
|
37
|
+
* when the break completes. */
|
|
38
|
+
readonly usesContentVideoElement?: boolean;
|
|
39
|
+
/** Wire the provider to the content element and the ad UI container.
|
|
40
|
+
* Called once before the first `requestAds`. */
|
|
41
|
+
attach(video: HTMLVideoElement, adContainer: HTMLElement, callbacks: AdsProviderCallbacks): void;
|
|
42
|
+
/** Request ads for the item's config. `autostart` carries the content's
|
|
43
|
+
* autoplay intent: false = hold the break until `startPendingBreak()`. */
|
|
44
|
+
requestAds(config: AdBreakConfig, autostart: boolean): void;
|
|
45
|
+
/** Start a loaded-but-held break. True if one was started. */
|
|
46
|
+
startPendingBreak(): boolean;
|
|
47
|
+
/** Content reached its end (so VMAP postrolls fire). */
|
|
48
|
+
contentDidComplete(): void;
|
|
49
|
+
pause(): void;
|
|
50
|
+
resume(): void;
|
|
51
|
+
skip(): void;
|
|
52
|
+
click(): void;
|
|
53
|
+
/** The player/container was resized (fullscreen, layout). */
|
|
54
|
+
resize(width: number, height: number, fullscreen: boolean): void;
|
|
55
|
+
destroy(): void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ads SPI — same shape as Android's `com.ogplayer.api.ads`. Web ad providers
|
|
3
|
+
* (Google IMA HTML5, native slot providers) plug in behind this; the first
|
|
4
|
+
* provider ships in a later release, the types are stable now so host code
|
|
5
|
+
* written against them doesn't churn.
|
|
6
|
+
*/
|
|
7
|
+
/** Marker for per-item ad configuration (mirrors Android `AdBreakConfig`). */
|
|
8
|
+
export interface AdBreakConfig {
|
|
9
|
+
}
|
|
10
|
+
/** A VAST/VMAP ad tag (consumed by an IMA-style provider). */
|
|
11
|
+
export interface AdTagConfig extends AdBreakConfig {
|
|
12
|
+
adTagUri: string;
|
|
13
|
+
}
|
|
14
|
+
export type AdBreakType = "PREROLL" | "MIDROLL" | "POSTROLL";
|
|
15
|
+
export interface AdInfo {
|
|
16
|
+
adId: string;
|
|
17
|
+
positionInPod: number;
|
|
18
|
+
podSize: number;
|
|
19
|
+
breakType: AdBreakType;
|
|
20
|
+
isSkippable: boolean;
|
|
21
|
+
skipOffsetMs: number;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
clickThroughUrl?: string;
|
|
24
|
+
providerRendersUi: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface OGAdError {
|
|
27
|
+
code: number;
|
|
28
|
+
phase: "LOAD" | "PLAY";
|
|
29
|
+
message: string;
|
|
30
|
+
}
|
|
31
|
+
export interface AdListener {
|
|
32
|
+
onAdBreakStarted?(breakType: AdBreakType, totalAds: number): void;
|
|
33
|
+
onAdStarted?(ad: AdInfo): void;
|
|
34
|
+
onAdSkipped?(ad: AdInfo): void;
|
|
35
|
+
onAdCompleted?(ad: AdInfo): void;
|
|
36
|
+
onAdBreakCompleted?(breakType: AdBreakType): void;
|
|
37
|
+
onAdPaused?(ad: AdInfo): void;
|
|
38
|
+
onAdResumed?(ad: AdInfo): void;
|
|
39
|
+
onAdProgress?(ad: AdInfo, positionMs: number, durationMs: number): void;
|
|
40
|
+
onAdSkippableStateChanged?(ad: AdInfo, isSkippable: boolean, skipOffsetMs: number): void;
|
|
41
|
+
onAdError?(error: OGAdError): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ErrorCategory, type OGPlayerError } from "./types.js";
|
|
2
|
+
export declare function makeError(code: number, category: ErrorCategory, message: string, retryable: boolean, httpStatusCode?: number, cause?: unknown): OGPlayerError;
|
|
3
|
+
/** Map the native `<video>` element MediaError. */
|
|
4
|
+
export declare function fromMediaError(err: MediaError | null): OGPlayerError;
|
|
5
|
+
/** Map an hls.js error event (data.type/details/response). */
|
|
6
|
+
export declare function fromHlsError(data: {
|
|
7
|
+
type: string;
|
|
8
|
+
details: string;
|
|
9
|
+
fatal: boolean;
|
|
10
|
+
response?: {
|
|
11
|
+
code?: number;
|
|
12
|
+
};
|
|
13
|
+
error?: Error;
|
|
14
|
+
}): OGPlayerError;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type OGPlayerError } from "./types.js";
|
|
2
|
+
export interface FairPlayConfig {
|
|
3
|
+
/** FairPlay Streaming license (CKC) endpoint. */
|
|
4
|
+
licenseUrl: string;
|
|
5
|
+
/** The FPS application certificate issued by Apple (binary .cer/.der). */
|
|
6
|
+
certificateUrl: string;
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
/** Fresh headers per license request (renewals included), merged over
|
|
9
|
+
* `headers` — the rotating-token hook (mobile parity). */
|
|
10
|
+
tokenProvider?: (request: {
|
|
11
|
+
licenseUrl: string;
|
|
12
|
+
renewal: boolean;
|
|
13
|
+
}) => Promise<Record<string, string>>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* FairPlay Streaming via standard EME on Safari's NATIVE HLS path — the web
|
|
17
|
+
* counterpart of the iOS SDK's `AVContentKeySession` flow: fetch the app
|
|
18
|
+
* certificate, then for every encrypted key request POST the SPC to the
|
|
19
|
+
* license server and feed the CKC back. Widevine/PlayReady run through
|
|
20
|
+
* hls.js's EME controller instead; this module only ever engages on Safari.
|
|
21
|
+
*
|
|
22
|
+
* Returns a detach function; errors are soft-reported through `onError`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function attachFairPlay(video: HTMLVideoElement, cfg: FairPlayConfig, onError: (e: OGPlayerError) => void, onKeysLoaded?: () => void, onSessionRenewed?: () => void): () => void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline license verification — the web counterpart of the Android/iOS
|
|
3
|
+
* `LicenseVerifier`, using the SAME ECDSA P-256 keypair: one license issued
|
|
4
|
+
* by the shared private key unlocks all three platforms. No network, ever.
|
|
5
|
+
*
|
|
6
|
+
* License format: `OGP1.<base64url payload JSON>.<base64url ECDSA sig>`.
|
|
7
|
+
* On the web the `apps` patterns bind to the page HOSTNAME (exact match, or
|
|
8
|
+
* prefix match when the pattern ends with `*`). Failure is soft — the caller
|
|
9
|
+
* shows the watermark, playback is unaffected.
|
|
10
|
+
*/
|
|
11
|
+
export type LicenseResult = {
|
|
12
|
+
licensed: true;
|
|
13
|
+
licensee: string;
|
|
14
|
+
edition: string;
|
|
15
|
+
exp: string;
|
|
16
|
+
} | {
|
|
17
|
+
licensed: false;
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function verifyLicense(licenseKey: string | undefined, hostname: string, publicKeyB64?: string): Promise<LicenseResult>;
|
|
21
|
+
/** DER SEQUENCE { INTEGER r, INTEGER s } → 64-byte raw r||s. */
|
|
22
|
+
export declare function derSignatureToRaw(der: Uint8Array): Uint8Array | null;
|
|
23
|
+
/** Exact match, or prefix match when the pattern ends with `*`. */
|
|
24
|
+
export declare function licensePatternMatches(host: string, pattern: string): boolean;
|
|
25
|
+
/** Valid through the end of `exp` (YYYY-MM-DD), device local date. */
|
|
26
|
+
export declare function isExpired(exp: string): boolean;
|