]>
Commit | Line | Data |
---|---|---|
1 | import * as Channel from 'jschannel' | |
2 | import { EventRegistrar } from './events' | |
3 | import { EventHandler, PlayerEventType, PeerTubeResolution } from './definitions' | |
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 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 embedElement: HTMLIFrameElement, | |
35 | private 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 | * @param value A number from 0 to 1 | |
94 | */ | |
95 | async setVolume (value: number) { | |
96 | await this.sendMessage('setVolume', value) | |
97 | } | |
98 | ||
99 | /** | |
100 | * Get the current volume level in the embed. | |
101 | * @param value A number from 0 to 1 | |
102 | */ | |
103 | async getVolume (): Promise<number> { | |
104 | return this.sendMessage<void, number>('getVolume') | |
105 | } | |
106 | ||
107 | /** | |
108 | * Tell the embed to seek to a specific position (in seconds) | |
109 | * @param seconds | |
110 | */ | |
111 | async seek (seconds: number) { | |
112 | await this.sendMessage('seek', seconds) | |
113 | } | |
114 | ||
115 | /** | |
116 | * Tell the embed to switch resolutions to the resolution identified | |
117 | * by the given ID. | |
118 | * | |
119 | * @param resolutionId The ID of the resolution as found with getResolutions() | |
120 | */ | |
121 | async setResolution (resolutionId: any) { | |
122 | await this.sendMessage('setResolution', resolutionId) | |
123 | } | |
124 | ||
125 | /** | |
126 | * Retrieve a list of the available resolutions. This may change later, listen to the | |
127 | * `resolutionUpdate` event with `addEventListener` in order to be updated as the available | |
128 | * resolutions change. | |
129 | */ | |
130 | async getResolutions (): Promise<PeerTubeResolution[]> { | |
131 | return this.sendMessage<void, PeerTubeResolution[]>('getResolutions') | |
132 | } | |
133 | ||
134 | /** | |
135 | * Retrieve a list of available playback rates. | |
136 | */ | |
137 | async getPlaybackRates (): Promise<number[]> { | |
138 | return this.sendMessage<void, number[]>('getPlaybackRates') | |
139 | } | |
140 | ||
141 | /** | |
142 | * Get the current playback rate. Defaults to 1 (1x playback rate). | |
143 | */ | |
144 | async getPlaybackRate (): Promise<number> { | |
145 | return this.sendMessage<void, number>('getPlaybackRate') | |
146 | } | |
147 | ||
148 | /** | |
149 | * Set the playback rate. Should be one of the options returned by getPlaybackRates(). | |
150 | * Passing 0.5 means half speed, 1 means normal, 2 means 2x speed, etc. | |
151 | * | |
152 | * @param rate | |
153 | */ | |
154 | async setPlaybackRate (rate: number) { | |
155 | await this.sendMessage('setPlaybackRate', rate) | |
156 | } | |
157 | ||
158 | private constructChannel () { | |
159 | this.channel = Channel.build({ | |
160 | window: this.embedElement.contentWindow, | |
161 | origin: '*', | |
162 | scope: this.scope || 'peertube' | |
163 | }) | |
164 | this.eventRegistrar.bindToChannel(this.channel) | |
165 | } | |
166 | ||
167 | private prepareToBeReady () { | |
168 | let readyResolve: Function | |
169 | let readyReject: Function | |
170 | ||
171 | this.readyPromise = new Promise<void>((res, rej) => { | |
172 | readyResolve = res | |
173 | readyReject = rej | |
174 | }) | |
175 | ||
176 | this.channel.bind('ready', success => success ? readyResolve() : readyReject()) | |
177 | this.channel.call({ | |
178 | method: 'isReady', | |
179 | success: isReady => isReady ? readyResolve() : null | |
180 | }) | |
181 | } | |
182 | ||
183 | private sendMessage<TIn, TOut> (method: string, params?: TIn): Promise<TOut> { | |
184 | return new Promise<TOut>((resolve, reject) => { | |
185 | this.channel.call({ | |
186 | method, params, | |
187 | success: result => resolve(result), | |
188 | error: error => reject(error) | |
189 | }) | |
190 | }) | |
191 | } | |
192 | } | |
193 | ||
194 | // put it on the window as well as the export | |
195 | (window[ 'PeerTubePlayer' ] as any) = PeerTubePlayer |