Fix is managaeble for channels
[oweals/peertube.git] / client / src / standalone / player / player.ts
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