1 // This may look like C code, but it's really -*- C++ -*-
2 /*
3  * Copyright (C) 2011 Emweb bv, Herent, Belgium.
4  *
5  * See the LICENSE file for terms of use.
6  */
7 #ifndef WMEDIA_PLAYER_H_
8 #define WMEDIA_PLAYER_H_
9 
10 #include <Wt/WCompositeWidget.h>
11 #include <Wt/WJavaScript.h>
12 #include <Wt/WLink.h>
13 #include <Wt/WString.h>
14 
15 namespace Wt {
16 
17 /*! \brief An enumeration for a media encoding.
18  *
19  * \sa addSource()
20  * \sa http://www.jplayer.org/latest/developer-guide/#jPlayer-media-encoding
21  */
22 enum class MediaEncoding {
23   PosterImage, //!< The poster image (e.g. JPG/PNG) for a video
24 
25   MP3,         //!< Audio: MP3 encoding (<b>essential audio</b> format)
26   M4A,         //!< Audio: MP4 encoding (<b>essential audio</b> format)
27   OGA,         //!< Audio: OGG encoding
28   WAV,         //!< Audio: WAV (uncompressed) format
29   WEBMA,       //!< Audio: WebM encoding
30   FLA,         //!< Audio: Flash format
31 
32   M4V,         //!< Video: MP4 encoding (<b>essential video</b> format)
33   OGV,         //!< Video: OGG encoding
34   WEBMV,       //!< Video: WebM encoding
35   FLV          //!< Video: Flash format
36 };
37 
38 /*! \brief An enumeration for a media type.
39  */
40 enum class MediaType {
41   Audio,       //!< Defines an audio player
42   Video        //!< Defines a video player
43 };
44 
45 /*! \brief An enumeration for a button function.
46  *
47  * \sa setButton(), button()
48  */
49 enum class MediaPlayerButtonId {
50   VideoPlay,     //!< Play button which overlays the video (for Video only)
51   Play,          //!< Play button, is hidden while playing
52   Pause,         //!< Pause button, is hidden while paused
53   Stop,          //!< Stop button
54   VolumeMute,    //!< Volume mute button
55   VolumeUnmute,  //!< Volume unmute button
56   VolumeMax,     //!< Volume max button
57 
58   /*! Toggle button for full screen, is hidden while full screen
59    * (for Video only) */
60   FullScreen,
61   /*! Toggle button to restore the screen, is shown only in full screen
62    * (for Video only) */
63   RestoreScreen,
64 
65   /*! Toggle button to enable looping, is hidden while repeating is on */
66   RepeatOn,
67   /*! Toggle button to disable looping, is hidden while repeat is off */
68   RepeatOff
69 };
70 
71 /*! \brief An enumeration for a progressbar function.
72  *
73  * \sa setProgressBar(), progressBar()
74  */
75 enum class MediaPlayerProgressBarId {
76   Time,  //!< The time bar
77   Volume //!< The volume bar
78 };
79 
80 /*! \brief An enumeration for a text.
81  *
82  * \sa setText(), text()
83  */
84 enum class MediaPlayerTextId {
85   CurrentTime, //!< Displays the current time
86   Duration,    //!< Displays the total duration
87   Title        //!< Displays the title set in setTitle()
88 };
89 
90 /*! \class WMediaPlayer Wt/WMediaPlayer.h Wt/WMediaPlayer.h
91  *  \brief A media player
92  *
93  * This widget implements a media player, suitable to play video or
94  * audio, and with a customizable user-interface.
95  *
96  * To support cross-browser playing of video or audio content, you may
97  * need to provide the contents appropriately encoded. For audio, at
98  * least an MP3 or MP4 audio (M4A) encoding should be supplied, while
99  * for video the M4V encoding should be provided. Additional encodings
100  * are beneficial since they increase the chance that native HTML
101  * <tt>&lt;video&gt;</tt> or <tt>&lt;audio&gt;</tt> elements can be
102  * used (which may be hardware accelerated), instead of the flash
103  * player. See <a
104  * href="http://www.jplayer.org/latest/developer-guide/#reference-html5-media">
105  * HTML5 browser media support</a>.
106  *
107  * You need to specify the encoding types you are going to use when
108  * instantiating the media player, since based on the chosen
109  * encodings, a particular suitable implementation will be used. Thus,
110  * you need to call addSource() immediately, but you may pass empty
111  * URLs if you do not yet want to load media.
112  *
113  * The player provides a user-interface to control the playback which
114  * may be freely customized, and which is independent of the
115  * underlying media technology (HTML video or Flash player). The
116  * controls user-interface may be implemented as a %Wt widget, where
117  * the controls (buttons, progress bars, and text widgets) are bound
118  * directly to the video player component (client-side).
119  *
120  * This widget relies on a third-party JavaScript component <a
121  * href="http://www.jplayer.org/">jPlayer</a>, which is
122  * distributed together with %Wt.
123  *
124  * The default user-interface can be themed using jPlayer themes. The
125  * theme is global (it applies to all media player instances), and is
126  * configured by loading a CSS stylesheet.
127  *
128  * The following code creates a video using the default controls:
129  * \if cpp
130  * \code
131  *   useStyleSheet(WApplication::relativeResourcesUrl() + "jPlayer/skin/jplayer.blue.monday.css");
132  *
133  *   auto player = std::make_unique<WMediaPlayer>(MediaType::Video);
134  *
135  *   player->addSource(MediaEncoding::M4V, "video.m4v");
136  *   player->addSource(MediaEncoding::OGV, "video.ogv");
137  *   player->addSource(MediaEncoding::PosterImage, "poster.png");
138  *
139  * \endcode
140  * \elseif java
141  * \code
142  * ...
143  * \endcode
144  * \endif
145  *
146  * Alternatively, a custom widget may be set which implements the
147  * controls, using setControlsWidget(). In this case, you should add
148  * to this widget the buttons, text place holders, and progress bars
149  * and bind them to the media player using the setButton(), setText()
150  * and setProgressBar() methods. The controls widget is integrated in
151  * the media player, and this has as unique benefit (for a video
152  * player) that they may also be shown when the video player is
153  * maximized.
154  *
155  * Finally, you may want to control the media player only through
156  * widgets external to the media player. This may be configured by
157  * setting \c 0 as controlsWidget. In this case however, full screen
158  * mode should not be used since there is no way to restore the
159  * original size.
160  */
161 class WT_API WMediaPlayer : public WCompositeWidget
162 {
163 public:
164   /*! \brief Typedef for enum Wt::MediaEncoding */
165   typedef MediaEncoding Encoding;
166   /*! \brief Typedef for enum Wt::MediaType */
167   typedef MediaType Type;
168   /*! \brief Typedef for enum Wt::MediaPlayerButtonId */
169   typedef MediaPlayerButtonId ButtonId;
170   /*! \brief Typedef for enum Wt::MediaPlayerProgressBarId */
171   typedef MediaPlayerProgressBarId ProgressBarId;
172   /*! \brief Typedef for enum Wt::MediaPlayerTextId */
173   typedef MediaPlayerTextId TextId;
174 
175   /*! \brief Creates a new media player.
176    *
177    * The player is instantiated with default controls.
178    *
179    * \sa setControlsWidget()
180    */
181   WMediaPlayer(MediaType mediaType);
182 
183   /*! \brief Destructor.
184    */
185   ~WMediaPlayer();
186 
187   /*! \brief Sets the video size.
188    *
189    * This sets the size for the video. The actual size of the media
190    * player may be slightly larger, if the controlWidget take
191    * additional space (i.e. is not overlayed on top of the video).
192    *
193    * CSS Themes for the default jPlayer controls support two formats
194    * (480 x 270 and 640 x 360).
195    *
196    * The default video size is 480 x 270.
197    */
198   void setVideoSize(int width, int height);
199 
200   /*! \brief Returns the video width.
201    *
202    * \sa setVideoSize()
203    */
videoWidth()204   int videoWidth() const { return videoWidth_; }
205 
206   /*! \brief Returns the video height.
207    *
208    * \sa setVideoSize()
209    */
videoHeight()210   int videoHeight() const { return videoHeight_; }
211 
212   /*! \brief Sets the user-interface controls widget.
213    *
214    * This sets a widget that contains the controls (buttons, text
215    * widgets, etc...) to allow the user to control the player.
216    *
217    * Widgets that implement the buttons, bars, and text holders should
218    * be bound to the player using setButton(), setText() and
219    * setProgressBar() calls.
220    *
221    * Setting a \c 0 widget will result in a player without
222    * controls. For an audio player this has the effect of being
223    * entirely invisible.
224    *
225    * The default controls widget is a widget that can be styled using
226    * a jPlayer CSS theme.
227    */
228   void setControlsWidget(std::unique_ptr<WWidget> controls);
229 
230   /*! \brief Returns the user-interface controls widget.
231    *
232    * \sa setControlsWidget()
233    */
234   WWidget *controlsWidget() const;
235 
236   /*! \brief Sets the media title.
237    *
238    * \sa MediaPlayerTextId::Title
239    */
240   void setTitle(const WString& title);
241 
242   /*! \brief Adds a source.
243    *
244    * Adds a media source. The source may be specified as a URL or as a
245    * dynamic resource.
246    *
247    * You may pass a null \p link if you want to indicate the media types
248    * you will use (later) without already loading data.
249    */
250   void addSource(MediaEncoding encoding, const WLink& link);
251 
252   /*! \brief Returns a source.
253    *
254    * Returns the media source for the given \p encoding, which must have
255    * previously been added using addSource().
256    */
257   WLink getSource(MediaEncoding encoding) const;
258 
259   /*! \brief Clears all sources.
260    *
261    * \sa addSource()
262    */
263   void clearSources();
264 
265   /*! \brief Binds a control button.
266    *
267    * A control button is typically implemented as a WAnchor or a
268    * WPushButton (although any WInteractWidget can work).
269    *
270    * You should use this method in conjunction with
271    * setControlsWidget() to bind buttons in a custom control interface
272    * to media player functions.
273    *
274    * The default control widget implements all buttons using a
275    * WAnchor.
276    */
277   void setButton(MediaPlayerButtonId id, WInteractWidget *btn);
278 
279   /*! \brief Returns a control button.
280    *
281    * \sa setButton()
282    */
283   WInteractWidget *button(MediaPlayerButtonId id) const;
284 
285   /*! \brief Binds a control progress bar.
286    *
287    * The progress bar for the MediaPlayerProgressBarId::Time
288    * indication should be contained in a WContainerWidget which bounds
289    * the width of the progress bar, rather than setting a width on the
290    * progress bar. This is because the progress bar may, in some
291    * cases, also be used to indicate which part of the media can be
292    * seeked, and for this its width is being manipulated.
293    *
294    * You should use this method in conjunction with
295    * setControlsWidget() to bind progress bars in a custom control
296    * interface to media player functions.
297    */
298   void setProgressBar(MediaPlayerProgressBarId id, WProgressBar *progressBar);
299 
300   /*! \brief Returns a control progress bar.
301    *
302    * \sa setProgressBar()
303    */
304   WProgressBar *progressBar(MediaPlayerProgressBarId id) const;
305 
306   /*! \brief Sets a text place-holder widget.
307    *
308    * This binds the widget that displays text such as current time and
309    * total duration of the loaded media.
310    *
311    * You should use this method in conjunction with
312    * setControlsWidget() to bind progress bars in a custom control
313    * interface to media player functions.
314    */
315   void setText(MediaPlayerTextId id, WText *text);
316 
317   /*! \brief Returns a text place-holder widget.
318    *
319    * \sa setText()
320    */
321   WText *text(MediaPlayerTextId id) const;
322 
323   /*! \brief Pauses the player.
324    *
325    * \sa play()
326    */
327   void pause();
328 
329   /*! \brief Start or resume playing.
330    *
331    * The player starts or resumes playing at the current time.
332    *
333    * \sa seek()
334    */
335   void play();
336 
337   /*! \brief Stops the player.
338    *
339    * \sa play()
340    */
341   void stop();
342 
343   /*! \brief Seeks to a time.
344    *
345    * If possible, the player sets the current time to the indicated \p time
346    * (expressed in seconds).
347    *
348    * \note It may be the case that this only works after the player has
349    *       already loaded the media.
350    */
351   void seek(double time);
352 
353   /*! \brief Sets the playback rate.
354    *
355    * This modifies the playback rate, expressed as a ratio of the
356    * normal (natural) playback rate.
357    *
358    * The default value is 1.0
359    *
360    * \note Not all browsers support this function.
361    */
362   void setPlaybackRate(double rate);
363 
364   /*! \brief Sets the volume.
365    *
366    * This modifies the volume, which must be a number between 0 and 1.0.
367    *
368    * The default value is 0.8
369    */
370   void setVolume(double volume);
371 
372   /*! \brief Returns the volume.
373    *
374    * \sa setVolume()
375    */
376   double volume() const;
377 
378   /*! \brief Mutes or unmutes the playback volume.
379    *
380    * \sa setVolume()
381    */
382   void mute(bool mute);
383 
384   /*! \brief Returns whether the media is currently playing.
385    *
386    * \sa play()
387    */
playing()388   bool playing() const { return status_.playing; }
389 
390   /*! \brief Returns the current player state.
391    *
392    * The state reflects in how far the media player has loaded the
393    * media, and has determined its characteristics.
394    *
395    */
readyState()396   MediaReadyState readyState() const { return status_.readyState; }
397 
398   /*! \brief Returns the duration.
399    *
400    * The duration may be reported as 0 if the player has not yet loaded
401    * the media to determine the duration. Otherwise the duration is the
402    * duration of the loaded media, expressed in seconds.
403    *
404    * \sa readyState(), currentTime()
405    */
duration()406   double duration() const { return status_.duration; }
407 
408   /*! \brief Returns the current playback time.
409    *
410    * Returns the current playback time, expressed in seconds.
411    *
412    * \sa seek()
413    */
currentTime()414   double currentTime() const { return status_.currentTime; }
415 
416   /*! \brief Returns the current playback rate.
417    *
418    * \sa setPlaybackRate()
419    */
playbackRate()420   double playbackRate() const { return status_.playbackRate; }
421 
422   // This even seems kind of useless ?
423   // JSignal<>& loadStarted();
424 
425   /*! \brief Event that indicates a time update.
426    *
427    * The event indicates that the currentTime() has changed.
428    */
429   JSignal<double>& timeUpdated();
430 
431   /*! \brief Event that indicates that playback started.
432    *
433    * The event is fired when playback has started (or is being
434    * continued).
435    */
436   JSignal<>& playbackStarted();
437 
438   /*! \brief Event that indicates that playback paused.
439    *
440    * The event is fired when playback has been paused.
441    */
442   JSignal<>& playbackPaused();
443 
444   /*! \brief Event that indicates that the video or audio has ended.
445    */
446   JSignal<>& ended();
447 
448   /*! \brief Event that indicates that the volume has changed.
449    */
450   JSignal<double>& volumeChanged();
451 
452   std::string jsPlayerRef() const;
453 
454   virtual void refresh() override;
455 
456 protected:
457   virtual void setFormData(const FormData& formData) override;
458   virtual void render(WFlags<RenderFlag> flags) override;
459 
460 private:
461   struct Source {
462     MediaEncoding encoding;
463     WLink link;
464   };
465 
466   struct SignalDouble {
467     JSignal<double> *signal;
468     std::string jsExprA1;
469   };
470 
471   std::vector<JSignal<> *> signals_;
472   std::vector<SignalDouble> signalsDouble_;
473 
474   MediaType mediaType_;
475   int videoWidth_, videoHeight_;
476 
477   WString title_;
478   std::vector<Source> media_;
479   std::string initialJs_;
480 
481   observing_ptr<WInteractWidget> control_[11];
482   WText *display_[3];
483   WProgressBar *progressBar_[2];
484 
485   observing_ptr<WWidget> gui_;
486   unsigned boundSignals_, boundSignalsDouble_;
487 
488   bool mediaUpdated_;
489 
490   struct State {
491     bool playing, ended;
492     MediaReadyState readyState;
493     double seekPercent, volume, duration, currentTime, playbackRate;
494 
495     State();
496   };
497 
498   State status_;
499 
500   void createDefaultGui();
501 
502   void addAnchor(WTemplate *t, MediaPlayerButtonId id, const char *bindId,
503 		 const std::string& styleClass,
504 		 const std::string& altText = std::string());
505   void addText(WTemplate *t, MediaPlayerTextId id, const char *bindId,
506 	       const std::string& styleClass);
507   void addProgressBar(WTemplate *t, MediaPlayerProgressBarId id,
508 		      const char *bindId,
509 		      const std::string& styleClass,
510 		      const std::string& valueStyleClass);
511   JSignal<>& signal(const char *name);
512   JSignal<double>& signalDouble(const char *name, const std::string& expr);
513 
514   void updateProgressBarState(MediaPlayerProgressBarId id);
515   void updateFromProgressBar(MediaPlayerProgressBarId id, double value);
516 
517   void playerDo(const std::string& method,
518 		const std::string& args = std::string());
519   void playerDoData(const std::string& method, const std::string& args);
520   void playerDoRaw(const std::string& jqueryMethod);
521 
522   static const char *LOAD_STARTED_SIGNAL;
523   static const char *TIME_UPDATED_SIGNAL;
524   static const char *PLAYBACK_STARTED_SIGNAL;
525   static const char *PLAYBACK_PAUSED_SIGNAL;
526   static const char *ENDED_SIGNAL;
527   static const char *VOLUME_CHANGED_SIGNAL;
528 
529   friend class WMediaPlayerImpl;
530 };
531 
532 }
533 
534 #endif // WMEDIA_PLAYER_H_
535 
536