diff options
author | Chocobozzz <me@florianbigard.com> | 2023-07-10 16:08:53 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2023-07-10 16:08:53 +0200 |
commit | 8953f055c86ca74f145d7ac5ac93bb6104d73af9 (patch) | |
tree | 4fd67ba6c2ba32f45bcc92e931cffe2fa57c12a4 /client/src/standalone/embed-player-api | |
parent | a1bd2b77d99cec5c27d38501f5f12f9dc339de17 (diff) | |
download | PeerTube-8953f055c86ca74f145d7ac5ac93bb6104d73af9.tar.gz PeerTube-8953f055c86ca74f145d7ac5ac93bb6104d73af9.tar.zst PeerTube-8953f055c86ca74f145d7ac5ac93bb6104d73af9.zip |
Rename player embed api
Diffstat (limited to 'client/src/standalone/embed-player-api')
-rw-r--r-- | client/src/standalone/embed-player-api/.npmignore | 3 | ||||
-rw-r--r-- | client/src/standalone/embed-player-api/README.md | 3 | ||||
-rw-r--r-- | client/src/standalone/embed-player-api/definitions.ts | 25 | ||||
-rw-r--r-- | client/src/standalone/embed-player-api/events.ts | 48 | ||||
-rw-r--r-- | client/src/standalone/embed-player-api/package.json | 28 | ||||
-rw-r--r-- | client/src/standalone/embed-player-api/player.ts | 236 | ||||
-rw-r--r-- | client/src/standalone/embed-player-api/tsconfig.json | 19 | ||||
-rw-r--r-- | client/src/standalone/embed-player-api/webpack.config.js | 12 |
8 files changed, 374 insertions, 0 deletions
diff --git a/client/src/standalone/embed-player-api/.npmignore b/client/src/standalone/embed-player-api/.npmignore new file mode 100644 index 000000000..870b6315b --- /dev/null +++ b/client/src/standalone/embed-player-api/.npmignore | |||
@@ -0,0 +1,3 @@ | |||
1 | tsconfig.json | ||
2 | *.ts | ||
3 | webpack.config.ts | ||
diff --git a/client/src/standalone/embed-player-api/README.md b/client/src/standalone/embed-player-api/README.md new file mode 100644 index 000000000..7b47e8f02 --- /dev/null +++ b/client/src/standalone/embed-player-api/README.md | |||
@@ -0,0 +1,3 @@ | |||
1 | # @peertube/embed-api | ||
2 | |||
3 | See https://docs.joinpeertube.org/api/embed-player | ||
diff --git a/client/src/standalone/embed-player-api/definitions.ts b/client/src/standalone/embed-player-api/definitions.ts new file mode 100644 index 000000000..495f1a98c --- /dev/null +++ b/client/src/standalone/embed-player-api/definitions.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | export type EventHandler<T> = (ev: T) => void | ||
2 | |||
3 | export type PlayerEventType = | ||
4 | 'pause' | 'play' | | ||
5 | 'playbackStatusUpdate' | | ||
6 | 'playbackStatusChange' | | ||
7 | 'resolutionUpdate' | | ||
8 | 'volumeChange' | ||
9 | |||
10 | export interface PeerTubeResolution { | ||
11 | id: any | ||
12 | label: string | ||
13 | active: boolean | ||
14 | height: number | ||
15 | |||
16 | src?: string | ||
17 | width?: number | ||
18 | } | ||
19 | |||
20 | export type PeerTubeTextTrack = { | ||
21 | id: string | ||
22 | label: string | ||
23 | src: string | ||
24 | mode: TextTrackMode | ||
25 | } | ||
diff --git a/client/src/standalone/embed-player-api/events.ts b/client/src/standalone/embed-player-api/events.ts new file mode 100644 index 000000000..77d21c78c --- /dev/null +++ b/client/src/standalone/embed-player-api/events.ts | |||
@@ -0,0 +1,48 @@ | |||
1 | import { EventHandler } from './definitions' | ||
2 | |||
3 | interface PlayerEventRegistrar { | ||
4 | registrations: EventHandler<any>[] | ||
5 | } | ||
6 | |||
7 | interface PlayerEventRegistrationMap { | ||
8 | [ name: string ]: PlayerEventRegistrar | ||
9 | } | ||
10 | |||
11 | export class EventRegistrar { | ||
12 | |||
13 | private eventRegistrations: PlayerEventRegistrationMap = {} | ||
14 | |||
15 | public bindToChannel (channel: Channel.MessagingChannel) { | ||
16 | for (const name of Object.keys(this.eventRegistrations)) { | ||
17 | channel.bind(name, (txn, params) => this.fire(name, params)) | ||
18 | } | ||
19 | } | ||
20 | |||
21 | public registerTypes (names: string[]) { | ||
22 | for (const name of names) { | ||
23 | this.eventRegistrations[name] = { registrations: [] } | ||
24 | } | ||
25 | } | ||
26 | |||
27 | public fire<T> (name: string, event: T) { | ||
28 | this.eventRegistrations[name].registrations.forEach(x => x(event)) | ||
29 | } | ||
30 | |||
31 | public addListener<T> (name: string, handler: EventHandler<T>) { | ||
32 | if (!this.eventRegistrations[name]) { | ||
33 | console.warn(`PeerTube: addEventListener(): The event '${name}' is not supported`) | ||
34 | return false | ||
35 | } | ||
36 | |||
37 | this.eventRegistrations[name].registrations.push(handler) | ||
38 | return true | ||
39 | } | ||
40 | |||
41 | public removeListener<T> (name: string, handler: EventHandler<T>) { | ||
42 | if (!this.eventRegistrations[name]) return false | ||
43 | |||
44 | this.eventRegistrations[name].registrations = this.eventRegistrations[name].registrations.filter(x => x !== handler) | ||
45 | |||
46 | return true | ||
47 | } | ||
48 | } | ||
diff --git a/client/src/standalone/embed-player-api/package.json b/client/src/standalone/embed-player-api/package.json new file mode 100644 index 000000000..b549fbf52 --- /dev/null +++ b/client/src/standalone/embed-player-api/package.json | |||
@@ -0,0 +1,28 @@ | |||
1 | { | ||
2 | "name": "@peertube/embed-api", | ||
3 | "private": false, | ||
4 | "version": "0.0.6", | ||
5 | "description": "API to communicate with the PeerTube player embed", | ||
6 | "scripts": { | ||
7 | "build": "../../../node_modules/.bin/tsc && ../../../node_modules/.bin/webpack --mode production --config ./webpack.config.js" | ||
8 | }, | ||
9 | "repository": { | ||
10 | "type": "git", | ||
11 | "url": "git+https://github.com/Chocobozzz/PeerTube.git" | ||
12 | }, | ||
13 | "keywords": [ | ||
14 | "peertube", | ||
15 | "embed" | ||
16 | ], | ||
17 | "main": "./dist/player.js", | ||
18 | "types": "./dist/player.d.ts", | ||
19 | "author": "Chocobozzz", | ||
20 | "license": "AGPL-3.0", | ||
21 | "bugs": { | ||
22 | "url": "https://github.com/Chocobozzz/PeerTube/issues" | ||
23 | }, | ||
24 | "homepage": "https://github.com/Chocobozzz/PeerTube#readme", | ||
25 | "dependencies": { | ||
26 | "jschannel": "^1.0.2" | ||
27 | } | ||
28 | } | ||
diff --git a/client/src/standalone/embed-player-api/player.ts b/client/src/standalone/embed-player-api/player.ts new file mode 100644 index 000000000..75487258b --- /dev/null +++ b/client/src/standalone/embed-player-api/player.ts | |||
@@ -0,0 +1,236 @@ | |||
1 | import * as Channel from 'jschannel' | ||
2 | import { EventHandler, PeerTubeResolution, PeerTubeTextTrack, PlayerEventType } from './definitions' | ||
3 | import { EventRegistrar } from './events' | ||
4 | |||
5 | const PASSTHROUGH_EVENTS = [ | ||
6 | 'pause', | ||
7 | 'play', | ||
8 | 'playbackStatusUpdate', | ||
9 | 'playbackStatusChange', | ||
10 | 'resolutionUpdate', | ||
11 | 'volumeChange' | ||
12 | ] | ||
13 | |||
14 | /** | ||
15 | * Allows for programmatic control of a PeerTube embed running in an <iframe> | ||
16 | * within a web page. | ||
17 | */ | ||
18 | export class PeerTubePlayer { | ||
19 | |||
20 | private readonly eventRegistrar: EventRegistrar = new EventRegistrar() | ||
21 | private channel: Channel.MessagingChannel | ||
22 | private readyPromise: Promise<void> | ||
23 | |||
24 | /** | ||
25 | * Construct a new PeerTubePlayer for the given PeerTube embed iframe. | ||
26 | * Optionally provide a `scope` to ensure that messages are not crossed | ||
27 | * between multiple PeerTube embeds. The string passed here must match the | ||
28 | * `scope=` query parameter on the embed URL. | ||
29 | * | ||
30 | * @param embedElement | ||
31 | * @param scope | ||
32 | */ | ||
33 | constructor ( | ||
34 | private readonly embedElement: HTMLIFrameElement, | ||
35 | private readonly scope?: string | ||
36 | ) { | ||
37 | this.eventRegistrar.registerTypes(PASSTHROUGH_EVENTS) | ||
38 | |||
39 | this.constructChannel() | ||
40 | this.prepareToBeReady() | ||
41 | } | ||
42 | |||
43 | /** | ||
44 | * Destroy the player object and remove the associated player from the DOM. | ||
45 | */ | ||
46 | destroy () { | ||
47 | this.embedElement.remove() | ||
48 | } | ||
49 | |||
50 | /** | ||
51 | * Listen to an event emitted by this player. | ||
52 | * | ||
53 | * @param event One of the supported event types | ||
54 | * @param handler A handler which will be passed an event object (or undefined if no event object is included) | ||
55 | */ | ||
56 | addEventListener (event: PlayerEventType, handler: EventHandler<any>): boolean { | ||
57 | return this.eventRegistrar.addListener(event, handler) | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Remove an event listener previously added with addEventListener(). | ||
62 | * | ||
63 | * @param event The name of the event previously listened to | ||
64 | * @param handler | ||
65 | */ | ||
66 | removeEventListener (event: PlayerEventType, handler: EventHandler<any>): boolean { | ||
67 | return this.eventRegistrar.removeListener(event, handler) | ||
68 | } | ||
69 | |||
70 | /** | ||
71 | * Promise resolves when the player is ready. | ||
72 | */ | ||
73 | get ready (): Promise<void> { | ||
74 | return this.readyPromise | ||
75 | } | ||
76 | |||
77 | /** | ||
78 | * Tell the embed to start/resume playback | ||
79 | */ | ||
80 | async play () { | ||
81 | await this.sendMessage('play') | ||
82 | } | ||
83 | |||
84 | /** | ||
85 | * Tell the embed to pause playback. | ||
86 | */ | ||
87 | async pause () { | ||
88 | await this.sendMessage('pause') | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * Tell the embed to change the audio volume | ||
93 | * | ||
94 | * @param value A number from 0 to 1 | ||
95 | */ | ||
96 | async setVolume (value: number) { | ||
97 | await this.sendMessage('setVolume', value) | ||
98 | } | ||
99 | |||
100 | /** | ||
101 | * Get the current volume level in the embed. | ||
102 | * | ||
103 | * @param value A number from 0 to 1 | ||
104 | */ | ||
105 | async getVolume (): Promise<number> { | ||
106 | return this.sendMessage<undefined, number>('getVolume') | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * Tell the embed to change the current caption | ||
111 | * | ||
112 | * @param value Caption id | ||
113 | */ | ||
114 | async setCaption (value: string) { | ||
115 | await this.sendMessage('setCaption', value) | ||
116 | } | ||
117 | |||
118 | /** | ||
119 | * Get video captions | ||
120 | */ | ||
121 | async getCaptions (): Promise<PeerTubeTextTrack[]> { | ||
122 | return this.sendMessage<undefined, PeerTubeTextTrack[]>('getCaptions') | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Tell the embed to seek to a specific position (in seconds) | ||
127 | * | ||
128 | * @param seconds | ||
129 | */ | ||
130 | async seek (seconds: number) { | ||
131 | await this.sendMessage('seek', seconds) | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Tell the embed to switch resolutions to the resolution identified | ||
136 | * by the given ID. | ||
137 | * | ||
138 | * @param resolutionId The ID of the resolution as found with getResolutions() | ||
139 | */ | ||
140 | async setResolution (resolutionId: any) { | ||
141 | await this.sendMessage('setResolution', resolutionId) | ||
142 | } | ||
143 | |||
144 | /** | ||
145 | * Retrieve a list of the available resolutions. This may change later, listen to the | ||
146 | * `resolutionUpdate` event with `addEventListener` in order to be updated as the available | ||
147 | * resolutions change. | ||
148 | */ | ||
149 | async getResolutions (): Promise<PeerTubeResolution[]> { | ||
150 | return this.sendMessage<undefined, PeerTubeResolution[]>('getResolutions') | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * Retrieve a list of available playback rates. | ||
155 | */ | ||
156 | async getPlaybackRates (): Promise<number[]> { | ||
157 | return this.sendMessage<undefined, number[]>('getPlaybackRates') | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * Get the current playback rate. Defaults to 1 (1x playback rate). | ||
162 | */ | ||
163 | async getPlaybackRate (): Promise<number> { | ||
164 | return this.sendMessage<undefined, number>('getPlaybackRate') | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Set the playback rate. Should be one of the options returned by getPlaybackRates(). | ||
169 | * Passing 0.5 means half speed, 1 means normal, 2 means 2x speed, etc. | ||
170 | * | ||
171 | * @param rate | ||
172 | */ | ||
173 | async setPlaybackRate (rate: number) { | ||
174 | await this.sendMessage('setPlaybackRate', rate) | ||
175 | } | ||
176 | |||
177 | /** | ||
178 | * Play next video in playlist | ||
179 | */ | ||
180 | async playNextVideo () { | ||
181 | await this.sendMessage('playNextVideo') | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * Play previous video in playlist | ||
186 | */ | ||
187 | async playPreviousVideo () { | ||
188 | await this.sendMessage('playPreviousVideo') | ||
189 | } | ||
190 | |||
191 | /** | ||
192 | * Get video position currently played (starts from 1) | ||
193 | */ | ||
194 | async getCurrentPosition () { | ||
195 | return this.sendMessage<undefined, number>('getCurrentPosition') | ||
196 | } | ||
197 | |||
198 | private constructChannel () { | ||
199 | this.channel = Channel.build({ | ||
200 | window: this.embedElement.contentWindow, | ||
201 | origin: '*', | ||
202 | scope: this.scope || 'peertube' | ||
203 | }) | ||
204 | this.eventRegistrar.bindToChannel(this.channel) | ||
205 | } | ||
206 | |||
207 | private prepareToBeReady () { | ||
208 | let readyResolve: () => void | ||
209 | let readyReject: () => void | ||
210 | |||
211 | this.readyPromise = new Promise<void>((res, rej) => { | ||
212 | readyResolve = res | ||
213 | readyReject = rej | ||
214 | }) | ||
215 | |||
216 | this.channel.bind('ready', success => success ? readyResolve() : readyReject()) | ||
217 | this.channel.call({ | ||
218 | method: 'isReady', | ||
219 | success: isReady => isReady ? readyResolve() : null | ||
220 | }) | ||
221 | } | ||
222 | |||
223 | private sendMessage<TIn, TOut> (method: string, params?: TIn): Promise<TOut> { | ||
224 | return new Promise<TOut>((resolve, reject) => { | ||
225 | this.channel.call({ | ||
226 | method, | ||
227 | params, | ||
228 | success: result => resolve(result), | ||
229 | error: error => reject(error) | ||
230 | }) | ||
231 | }) | ||
232 | } | ||
233 | } | ||
234 | |||
235 | // put it on the window as well as the export | ||
236 | (window as any)['PeerTubePlayer'] = PeerTubePlayer | ||
diff --git a/client/src/standalone/embed-player-api/tsconfig.json b/client/src/standalone/embed-player-api/tsconfig.json new file mode 100644 index 000000000..eecc63dfb --- /dev/null +++ b/client/src/standalone/embed-player-api/tsconfig.json | |||
@@ -0,0 +1,19 @@ | |||
1 | { | ||
2 | "compilerOptions": { | ||
3 | "module": "commonjs", | ||
4 | "removeComments": true, | ||
5 | "sourceMap": false, | ||
6 | "typeRoots": [ | ||
7 | "../../../node_modules/@types" | ||
8 | ], | ||
9 | "outDir": "./dist", | ||
10 | "declaration": true, | ||
11 | "target": "es5", | ||
12 | "types": [], | ||
13 | "lib": [ | ||
14 | "es2018", | ||
15 | "dom" | ||
16 | ] | ||
17 | }, | ||
18 | "files": [ "./player.ts" ] | ||
19 | } | ||
diff --git a/client/src/standalone/embed-player-api/webpack.config.js b/client/src/standalone/embed-player-api/webpack.config.js new file mode 100644 index 000000000..48d350edf --- /dev/null +++ b/client/src/standalone/embed-player-api/webpack.config.js | |||
@@ -0,0 +1,12 @@ | |||
1 | const path = require('path') | ||
2 | |||
3 | module.exports = [ | ||
4 | { | ||
5 | mode: 'production', | ||
6 | entry: './dist/player.js', | ||
7 | output: { | ||
8 | filename: 'player.min.js', | ||
9 | path: path.resolve(__dirname, 'build') | ||
10 | } | ||
11 | } | ||
12 | ] | ||