aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/standalone/player
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/standalone/player')
-rw-r--r--client/src/standalone/player/definitions.ts18
-rw-r--r--client/src/standalone/player/events.ts48
-rw-r--r--client/src/standalone/player/player.ts190
3 files changed, 256 insertions, 0 deletions
diff --git a/client/src/standalone/player/definitions.ts b/client/src/standalone/player/definitions.ts
new file mode 100644
index 000000000..6920672a7
--- /dev/null
+++ b/client/src/standalone/player/definitions.ts
@@ -0,0 +1,18 @@
1
2export interface EventHandler<T> {
3 (ev : T) : void
4}
5
6export type PlayerEventType =
7 'pause' | 'play' |
8 'playbackStatusUpdate' |
9 'playbackStatusChange' |
10 'resolutionUpdate'
11;
12
13export interface PeerTubeResolution {
14 id : any
15 label : string
16 src : string
17 active : boolean
18} \ No newline at end of file
diff --git a/client/src/standalone/player/events.ts b/client/src/standalone/player/events.ts
new file mode 100644
index 000000000..c01328352
--- /dev/null
+++ b/client/src/standalone/player/events.ts
@@ -0,0 +1,48 @@
1import { EventHandler } from "./definitions"
2
3interface PlayerEventRegistrar {
4 registrations : Function[]
5}
6
7interface PlayerEventRegistrationMap {
8 [name : string] : PlayerEventRegistrar
9}
10
11export class EventRegistrar {
12
13 private eventRegistrations : PlayerEventRegistrationMap = {}
14
15 public bindToChannel(channel : Channel.MessagingChannel) {
16 for (let name of Object.keys(this.eventRegistrations))
17 channel.bind(name, (txn, params) => this.fire(name, params))
18 }
19
20 public registerTypes(names : string[]) {
21 for (let name of names)
22 this.eventRegistrations[name] = { registrations: [] }
23 }
24
25 public fire<T>(name : string, event : T) {
26 this.eventRegistrations[name].registrations.forEach(x => x(event))
27 }
28
29 public addListener<T>(name : string, handler : EventHandler<T>) {
30 if (!this.eventRegistrations[name]) {
31 console.warn(`PeerTube: addEventListener(): The event '${name}' is not supported`)
32 return false
33 }
34
35 this.eventRegistrations[name].registrations.push(handler)
36 return true
37 }
38
39 public removeListener<T>(name : string, handler : EventHandler<T>) {
40 if (!this.eventRegistrations[name])
41 return false
42
43 this.eventRegistrations[name].registrations =
44 this.eventRegistrations[name].registrations.filter(x => x === handler)
45
46 return true
47 }
48}
diff --git a/client/src/standalone/player/player.ts b/client/src/standalone/player/player.ts
new file mode 100644
index 000000000..9fc648d25
--- /dev/null
+++ b/client/src/standalone/player/player.ts
@@ -0,0 +1,190 @@
1import * as Channel from 'jschannel'
2import { EventRegistrar } from './events'
3import { EventHandler, PlayerEventType, PeerTubeResolution } from './definitions'
4
5const PASSTHROUGH_EVENTS = [
6 'pause', 'play',
7 'playbackStatusUpdate',
8 'playbackStatusChange',
9 'resolutionUpdate'
10]
11
12/**
13 * Allows for programmatic control of a PeerTube embed running in an <iframe>
14 * within a web page.
15 */
16export class PeerTubePlayer {
17 /**
18 * Construct a new PeerTubePlayer for the given PeerTube embed iframe.
19 * Optionally provide a `scope` to ensure that messages are not crossed
20 * between multiple PeerTube embeds. The string passed here must match the
21 * `scope=` query parameter on the embed URL.
22 *
23 * @param embedElement
24 * @param scope
25 */
26 constructor(
27 private embedElement : HTMLIFrameElement,
28 private scope? : string
29 ) {
30 this.eventRegistrar.registerTypes(PASSTHROUGH_EVENTS)
31
32 this.constructChannel()
33 this.prepareToBeReady()
34 }
35
36 private eventRegistrar : EventRegistrar = new EventRegistrar()
37 private channel : Channel.MessagingChannel
38 private readyPromise : Promise<void>
39
40 /**
41 * Destroy the player object and remove the associated player from the DOM.
42 */
43 destroy() {
44 this.embedElement.remove()
45 }
46
47 /**
48 * Listen to an event emitted by this player.
49 *
50 * @param event One of the supported event types
51 * @param handler A handler which will be passed an event object (or undefined if no event object is included)
52 */
53 addEventListener(event : PlayerEventType, handler : EventHandler<any>): boolean {
54 return this.eventRegistrar.addListener(event, handler)
55 }
56
57 /**
58 * Remove an event listener previously added with addEventListener().
59 *
60 * @param event The name of the event previously listened to
61 * @param handler
62 */
63 removeEventListener(event : PlayerEventType, handler : EventHandler<any>): boolean {
64 return this.eventRegistrar.removeListener(event, handler)
65 }
66
67 /**
68 * Promise resolves when the player is ready.
69 */
70 get ready(): Promise<void> {
71 return this.readyPromise
72 }
73
74 /**
75 * Tell the embed to start/resume playback
76 */
77 async play() {
78 await this.sendMessage('play')
79 }
80
81 /**
82 * Tell the embed to pause playback.
83 */
84 async pause() {
85 await this.sendMessage('pause')
86 }
87
88 /**
89 * Tell the embed to change the audio volume
90 * @param value A number from 0 to 1
91 */
92 async setVolume(value : number) {
93 await this.sendMessage('setVolume', value)
94 }
95
96 /**
97 * Get the current volume level in the embed.
98 * @param value A number from 0 to 1
99 */
100 async getVolume(): Promise<number> {
101 return await this.sendMessage<void, number>('setVolume')
102 }
103
104 /**
105 * Tell the embed to seek to a specific position (in seconds)
106 * @param seconds
107 */
108 async seek(seconds : number) {
109 await this.sendMessage('seek', seconds)
110 }
111
112 /**
113 * Tell the embed to switch resolutions to the resolution identified
114 * by the given ID.
115 *
116 * @param resolutionId The ID of the resolution as found with getResolutions()
117 */
118 async setResolution(resolutionId : any) {
119 await this.sendMessage('setResolution', resolutionId)
120 }
121
122 /**
123 * Retrieve a list of the available resolutions. This may change later, listen to the
124 * `resolutionUpdate` event with `addEventListener` in order to be updated as the available
125 * resolutions change.
126 */
127 async getResolutions(): Promise<PeerTubeResolution[]> {
128 return await this.sendMessage<void, PeerTubeResolution[]>('getResolutions')
129 }
130
131 /**
132 * Retrieve a list of available playback rates.
133 */
134 async getPlaybackRates() : Promise<number[]> {
135 return await this.sendMessage<void, number[]>('getPlaybackRates')
136 }
137
138 /**
139 * Get the current playback rate. Defaults to 1 (1x playback rate).
140 */
141 async getPlaybackRate() : Promise<number> {
142 return await this.sendMessage<void, number>('getPlaybackRate')
143 }
144
145 /**
146 * Set the playback rate. Should be one of the options returned by getPlaybackRates().
147 * Passing 0.5 means half speed, 1 means normal, 2 means 2x speed, etc.
148 *
149 * @param rate
150 */
151 async setPlaybackRate(rate : number) {
152 await this.sendMessage('setPlaybackRate', rate)
153 }
154
155 private constructChannel() {
156 this.channel = Channel.build({
157 window: this.embedElement.contentWindow,
158 origin: '*',
159 scope: this.scope || 'peertube'
160 })
161 this.eventRegistrar.bindToChannel(this.channel)
162 }
163
164 private prepareToBeReady() {
165 let readyResolve, readyReject
166 this.readyPromise = new Promise<void>((res, rej) => {
167 readyResolve = res
168 readyReject = rej
169 })
170
171 this.channel.bind('ready', success => success ? readyResolve() : readyReject())
172 this.channel.call({
173 method: 'isReady',
174 success: isReady => isReady ? readyResolve() : null
175 })
176 }
177
178 private sendMessage<TIn, TOut>(method : string, params? : TIn): Promise<TOut> {
179 return new Promise<TOut>((resolve, reject) => {
180 this.channel.call({
181 method, params,
182 success: result => resolve(result),
183 error: error => reject(error)
184 })
185 })
186 }
187}
188
189// put it on the window as well as the export
190window['PeerTubePlayer'] = PeerTubePlayer \ No newline at end of file