46813be7596455f5c9348be8721088056156885c
[WebKit-https.git] / Source / WebCore / platform / graphics / gtk / FullscreenVideoControllerGtk.cpp
1 /*
2  *  Copyright (C) 2013 Igalia S.L
3  *
4  *  This library is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Library General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2 of the License, or (at your option) any later version.
8  *
9  *  This library is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Library General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Library General Public License
15  *  along with this library; see the file COPYING.LIB.  If not, write to
16  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  *  Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21
22 #if ENABLE(VIDEO) && USE(GSTREAMER) && USE(NATIVE_FULLSCREEN_VIDEO)
23
24 #include "FullscreenVideoControllerGtk.h"
25
26 #include "FullscreenVideoControllerGStreamer.h"
27 #include "GRefPtrGtk.h"
28 #include "GStreamerGWorld.h"
29 #include "GtkVersioning.h"
30 #include "MediaPlayer.h"
31 #include "MediaPlayerPrivateGStreamerBase.h"
32
33 #include <gdk/gdk.h>
34 #include <gdk/gdkkeysyms.h>
35 #include <glib/gi18n-lib.h>
36 #include <gtk/gtk.h>
37 #include <wtf/text/CString.h>
38
39 #define HUD_AUTO_HIDE_INTERVAL 3000 // 3 seconds
40 #define PROGRESS_BAR_UPDATE_INTERVAL 150 // 150ms
41
42 // Use symbolic icons only if we build with GTK+-3 support. They could
43 // be enabled for the GTK+2 build but we'd need to bump the required
44 // version to at least 2.22.
45 #if GTK_MAJOR_VERSION > 2
46 #define ICON_NAME_SUFFIX "-symbolic"
47 #else
48 #define ICON_NAME_SUFFIX
49 #endif
50
51 #define PLAY_ICON_NAME "media-playback-start"ICON_NAME_SUFFIX
52 #define PAUSE_ICON_NAME "media-playback-pause"ICON_NAME_SUFFIX
53 #define EXIT_FULLSCREEN_ICON_NAME "view-restore"ICON_NAME_SUFFIX
54
55 namespace WebCore {
56
57 static gboolean hideHudCallback(FullscreenVideoControllerGtk* controller)
58 {
59     controller->hideHud();
60     return FALSE;
61 }
62
63 static gboolean onFullscreenGtkMotionNotifyEvent(GtkWidget* widget, GdkEventMotion* event,  FullscreenVideoControllerGtk* controller)
64 {
65     controller->showHud(true);
66     return TRUE;
67 }
68
69 static void onFullscreenGtkActiveNotification(GtkWidget* widget, GParamSpec* property, FullscreenVideoControllerGtk* controller)
70 {
71     if (!gtk_window_is_active(GTK_WINDOW(widget)))
72         controller->hideHud();
73 }
74
75 static gboolean onFullscreenGtkConfigureEvent(GtkWidget* widget, GdkEventConfigure* event, FullscreenVideoControllerGtk* controller)
76 {
77     controller->gtkConfigure(event);
78     return TRUE;
79 }
80
81 static void onFullscreenGtkDestroy(GtkWidget* widget, FullscreenVideoControllerGtk* controller)
82 {
83     controller->exitFullscreen();
84 }
85
86 static void togglePlayPauseActivated(GtkAction* action, FullscreenVideoControllerGtk* controller)
87 {
88     controller->togglePlay();
89 }
90
91 static void exitFullscreenActivated(GtkAction* action, FullscreenVideoControllerGtk* controller)
92 {
93     controller->exitOnUserRequest();
94 }
95
96 static gboolean progressBarUpdateCallback(FullscreenVideoControllerGtk* controller)
97 {
98     return controller->updateHudProgressBar();
99 }
100
101 static gboolean timeScaleButtonPressed(GtkWidget* widget, GdkEventButton* event, FullscreenVideoControllerGtk* controller)
102 {
103     if (event->type != GDK_BUTTON_PRESS)
104         return FALSE;
105
106     controller->beginSeek();
107     return FALSE;
108 }
109
110 static gboolean timeScaleButtonReleased(GtkWidget* widget, GdkEventButton* event, FullscreenVideoControllerGtk* controller)
111 {
112     controller->endSeek();
113     return FALSE;
114 }
115
116 static void timeScaleValueChanged(GtkWidget* widget, FullscreenVideoControllerGtk* controller)
117 {
118     controller->doSeek();
119 }
120
121 static void volumeValueChanged(GtkScaleButton *button, gdouble value, FullscreenVideoControllerGtk* controller)
122 {
123     controller->setVolume(static_cast<float>(value));
124 }
125
126
127 FullscreenVideoControllerGtk::FullscreenVideoControllerGtk(MediaPlayerPrivateGStreamerBase* player)
128     : FullscreenVideoControllerGStreamer(player)
129     , m_hudTimeoutId(0)
130     , m_progressBarUpdateId(0)
131     , m_seekLock(false)
132     , m_window(0)
133     , m_hudWindow(0)
134     , m_volumeButton(0)
135     , m_keyPressSignalId(0)
136     , m_destroySignalId(0)
137     , m_isActiveSignalId(0)
138     , m_motionNotifySignalId(0)
139     , m_configureEventSignalId(0)
140     , m_hudMotionNotifySignalId(0)
141     , m_timeScaleButtonPressedSignalId(0)
142     , m_timeScaleButtonReleasedSignalId(0)
143     , m_playActionActivateSignalId(0)
144     , m_exitFullcreenActionActivateSignalId(0)
145 {
146 }
147
148 void FullscreenVideoControllerGtk::gtkConfigure(GdkEventConfigure* event)
149 {
150     updateHudPosition();
151 }
152
153 void FullscreenVideoControllerGtk::showHud(bool autoHide)
154 {
155     if (!m_hudWindow)
156         return;
157
158     if (m_hudTimeoutId) {
159         g_source_remove(m_hudTimeoutId);
160         m_hudTimeoutId = 0;
161     }
162
163     // Show the cursor.
164     GdkWindow* window = gtk_widget_get_window(m_window);
165     gdk_window_set_cursor(window, 0);
166
167     // Update the progress bar immediately before showing the window.
168     updateHudProgressBar();
169     gtk_widget_show_all(m_hudWindow);
170     updateHudPosition();
171
172     // Start periodic updates of the progress bar.
173     if (!m_progressBarUpdateId)
174         m_progressBarUpdateId = g_timeout_add(PROGRESS_BAR_UPDATE_INTERVAL, reinterpret_cast<GSourceFunc>(progressBarUpdateCallback), this);
175
176     // Hide the hud in few seconds, if requested.
177     if (autoHide)
178         m_hudTimeoutId = g_timeout_add(HUD_AUTO_HIDE_INTERVAL, reinterpret_cast<GSourceFunc>(hideHudCallback), this);
179 }
180
181 void FullscreenVideoControllerGtk::hideHud()
182 {
183     if (m_hudTimeoutId) {
184         g_source_remove(m_hudTimeoutId);
185         m_hudTimeoutId = 0;
186     }
187
188     if (!m_hudWindow)
189         return;
190
191     // Keep the hud visible if a seek is in progress or if the volume
192     // popup is visible.
193     GtkWidget* volumePopup = gtk_scale_button_get_popup(GTK_SCALE_BUTTON(m_volumeButton));
194     if (m_seekLock || gtk_widget_get_visible(volumePopup)) {
195         showHud(true);
196         return;
197     }
198
199     GdkWindow* window = gtk_widget_get_window(m_window);
200     GRefPtr<GdkCursor> cursor = adoptGRef(gdk_cursor_new(GDK_BLANK_CURSOR));
201     gdk_window_set_cursor(window, cursor.get());
202
203     gtk_widget_hide(m_hudWindow);
204
205     if (m_progressBarUpdateId) {
206         g_source_remove(m_progressBarUpdateId);
207         m_progressBarUpdateId = 0;
208     }
209 }
210
211 static gboolean onFullscreenGtkKeyPressEvent(GtkWidget* widget, GdkEventKey* event, FullscreenVideoControllerGtk* controller)
212 {
213     switch (event->keyval) {
214     case GDK_Escape:
215         controller->exitOnUserRequest();
216         break;
217     case GDK_space:
218     case GDK_Return:
219         controller->togglePlay();
220         break;
221     case GDK_Up:
222         controller->increaseVolume();
223         break;
224     case GDK_Down:
225         controller->decreaseVolume();
226         break;
227     default:
228         break;
229     }
230
231     return TRUE;
232 }
233
234 void FullscreenVideoControllerGtk::initializeWindow()
235 {
236     m_window = reinterpret_cast<GtkWidget*>(m_gstreamerGWorld->platformVideoWindow()->window());
237
238     if (!m_hudWindow)
239         createHud();
240
241     // Ensure black background.
242 #ifdef GTK_API_VERSION_2
243     GdkColor color = { 1, 0, 0, 0 };
244     gtk_widget_modify_bg(m_window, GTK_STATE_NORMAL, &color);
245 #else
246     GdkRGBA color = { 0, 0, 0, 1};
247     gtk_widget_override_background_color(m_window, GTK_STATE_FLAG_NORMAL, &color);
248 #endif
249     gtk_widget_set_double_buffered(m_window, FALSE);
250
251     m_keyPressSignalId = g_signal_connect(m_window, "key-press-event", G_CALLBACK(onFullscreenGtkKeyPressEvent), this);
252     m_destroySignalId = g_signal_connect(m_window, "destroy", G_CALLBACK(onFullscreenGtkDestroy), this);
253     m_isActiveSignalId = g_signal_connect(m_window, "notify::is-active", G_CALLBACK(onFullscreenGtkActiveNotification), this);
254
255     gtk_widget_show_all(m_window);
256
257     GdkWindow* window = gtk_widget_get_window(m_window);
258     GRefPtr<GdkCursor> cursor = adoptGRef(gdk_cursor_new(GDK_BLANK_CURSOR));
259     gdk_window_set_cursor(window, cursor.get());
260
261     m_motionNotifySignalId = g_signal_connect(m_window, "motion-notify-event", G_CALLBACK(onFullscreenGtkMotionNotifyEvent), this);
262     m_configureEventSignalId = g_signal_connect(m_window, "configure-event", G_CALLBACK(onFullscreenGtkConfigureEvent), this);
263
264     gtk_window_fullscreen(GTK_WINDOW(m_window));
265     showHud(true);
266 }
267
268 void FullscreenVideoControllerGtk::updateHudPosition()
269 {
270     if (!m_hudWindow)
271         return;
272
273     // Get the screen rectangle.
274     GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_window));
275     GdkWindow* window = gtk_widget_get_window(m_window);
276     GdkRectangle fullscreenRectangle;
277     gdk_screen_get_monitor_geometry(screen, gdk_screen_get_monitor_at_window(screen, window), &fullscreenRectangle);
278
279     // Get the popup window size.
280     int hudWidth, hudHeight;
281     gtk_window_get_size(GTK_WINDOW(m_hudWindow), &hudWidth, &hudHeight);
282
283     // Resize the hud to the full width of the screen.
284     gtk_window_resize(GTK_WINDOW(m_hudWindow), fullscreenRectangle.width, hudHeight);
285
286     // Move the hud to the bottom of the screen.
287     gtk_window_move(GTK_WINDOW(m_hudWindow), fullscreenRectangle.x, fullscreenRectangle.height + fullscreenRectangle.y - hudHeight);
288 }
289
290 void FullscreenVideoControllerGtk::destroyWindow()
291 {
292     if (!m_hudWindow)
293         return;
294
295     g_signal_handler_disconnect(m_window, m_keyPressSignalId);
296     g_signal_handler_disconnect(m_window, m_destroySignalId);
297     g_signal_handler_disconnect(m_window, m_isActiveSignalId);
298     g_signal_handler_disconnect(m_window, m_motionNotifySignalId);
299     g_signal_handler_disconnect(m_window, m_configureEventSignalId);
300     g_signal_handler_disconnect(m_hudWindow, m_hudMotionNotifySignalId);
301     g_signal_handler_disconnect(m_timeHScale, m_timeScaleButtonPressedSignalId);
302     g_signal_handler_disconnect(m_timeHScale, m_timeScaleButtonReleasedSignalId);
303     g_signal_handler_disconnect(m_timeHScale, m_hscaleUpdateId);
304     g_signal_handler_disconnect(m_volumeButton, m_volumeUpdateId);
305     g_signal_handler_disconnect(m_playPauseAction, m_playActionActivateSignalId);
306     g_signal_handler_disconnect(m_exitFullscreenAction, m_exitFullcreenActionActivateSignalId);
307
308     if (m_hudTimeoutId) {
309         g_source_remove(m_hudTimeoutId);
310         m_hudTimeoutId = 0;
311     }
312
313     if (m_progressBarUpdateId) {
314         g_source_remove(m_progressBarUpdateId);
315         m_progressBarUpdateId = 0;
316     }
317
318     gtk_widget_hide(m_window);
319
320     if (m_hudWindow)
321         gtk_widget_destroy(m_hudWindow);
322     m_hudWindow = 0;
323 }
324
325 void FullscreenVideoControllerGtk::playStateChanged()
326 {
327     if (m_client->mediaPlayerIsPaused())
328         g_object_set(m_playPauseAction, "tooltip", _("Play"), "icon-name", PLAY_ICON_NAME, NULL);
329     else
330         g_object_set(m_playPauseAction, "tooltip", _("Pause"), "icon-name", PAUSE_ICON_NAME, NULL);
331     showHud(!m_client->mediaPlayerIsPaused());
332 }
333
334 void FullscreenVideoControllerGtk::volumeChanged()
335 {
336     if (!m_volumeButton)
337         return;
338
339     g_signal_handler_block(m_volumeButton, m_volumeUpdateId);
340     gtk_scale_button_set_value(GTK_SCALE_BUTTON(m_volumeButton), m_player->volume());
341     g_signal_handler_unblock(m_volumeButton, m_volumeUpdateId);
342 }
343
344 void FullscreenVideoControllerGtk::muteChanged()
345 {
346     if (!m_volumeButton)
347         return;
348
349     g_signal_handler_block(m_volumeButton, m_volumeUpdateId);
350     gtk_scale_button_set_value(GTK_SCALE_BUTTON(m_volumeButton), m_player->muted() ? 0 : m_player->volume());
351     g_signal_handler_unblock(m_volumeButton, m_volumeUpdateId);
352 }
353
354 void FullscreenVideoControllerGtk::beginSeek()
355 {
356     m_seekLock = true;
357 }
358
359 void FullscreenVideoControllerGtk::doSeek()
360 {
361     if (!m_seekLock)
362         return;
363
364     m_player->seek(gtk_range_get_value(GTK_RANGE(m_timeHScale))*m_player->duration() / 100);
365 }
366
367 void FullscreenVideoControllerGtk::endSeek()
368 {
369     m_seekLock = false;
370 }
371
372 gboolean FullscreenVideoControllerGtk::updateHudProgressBar()
373 {
374     float mediaDuration(m_player->duration());
375     float mediaPosition(m_player->currentTime());
376
377     if (!m_seekLock) {
378         gdouble value = 0.0;
379
380         if (mediaPosition && mediaDuration)
381             value = (mediaPosition * 100.0) / mediaDuration;
382
383         GtkAdjustment* adjustment = gtk_range_get_adjustment(GTK_RANGE(m_timeHScale));
384         gtk_adjustment_set_value(adjustment, value);
385     }
386
387     gtk_range_set_fill_level(GTK_RANGE(m_timeHScale), (m_player->maxTimeLoaded() / mediaDuration)* 100);
388
389     gchar* label = g_strdup_printf("%s / %s", timeToString(mediaPosition).utf8().data(), timeToString(mediaDuration).utf8().data());
390     gtk_label_set_text(GTK_LABEL(m_timeLabel), label);
391     g_free(label);
392     return TRUE;
393 }
394
395 void FullscreenVideoControllerGtk::createHud()
396 {
397     m_hudWindow = gtk_window_new(GTK_WINDOW_POPUP);
398     gtk_window_set_gravity(GTK_WINDOW(m_hudWindow), GDK_GRAVITY_SOUTH_WEST);
399     gtk_window_set_type_hint(GTK_WINDOW(m_hudWindow), GDK_WINDOW_TYPE_HINT_NORMAL);
400
401     m_hudMotionNotifySignalId = g_signal_connect(m_hudWindow, "motion-notify-event", G_CALLBACK(onFullscreenGtkMotionNotifyEvent), this);
402
403 #ifdef GTK_API_VERSION_2
404     GtkWidget* hbox = gtk_hbox_new(FALSE, 4);
405 #else
406     GtkWidget* hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
407 #endif
408     gtk_container_add(GTK_CONTAINER(m_hudWindow), hbox);
409
410     m_playPauseAction = gtk_action_new("play", _("Play / Pause"), _("Play or pause the media"), PAUSE_ICON_NAME);
411     m_playActionActivateSignalId = g_signal_connect(m_playPauseAction, "activate", G_CALLBACK(togglePlayPauseActivated), this);
412
413     GtkWidget* item = gtk_action_create_tool_item(m_playPauseAction);
414     gtk_box_pack_start(GTK_BOX(hbox), item, FALSE, TRUE, 0);
415
416     GtkWidget* label = gtk_label_new(_("Time:"));
417     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
418
419     GtkAdjustment* adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 100.0, 0.1, 1.0, 1.0));
420 #ifdef GTK_API_VERSION_2
421     m_timeHScale = gtk_hscale_new(adjustment);
422 #else
423     m_timeHScale = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, adjustment);
424 #endif
425     gtk_scale_set_draw_value(GTK_SCALE(m_timeHScale), FALSE);
426     gtk_range_set_show_fill_level(GTK_RANGE(m_timeHScale), TRUE);
427     m_timeScaleButtonPressedSignalId = g_signal_connect(m_timeHScale, "button-press-event", G_CALLBACK(timeScaleButtonPressed), this);
428     m_timeScaleButtonReleasedSignalId = g_signal_connect(m_timeHScale, "button-release-event", G_CALLBACK(timeScaleButtonReleased), this);
429     m_hscaleUpdateId = g_signal_connect(m_timeHScale, "value-changed", G_CALLBACK(timeScaleValueChanged), this);
430
431     gtk_box_pack_start(GTK_BOX(hbox), m_timeHScale, TRUE, TRUE, 0);
432
433     m_timeLabel = gtk_label_new("");
434     gtk_box_pack_start(GTK_BOX(hbox), m_timeLabel, FALSE, TRUE, 0);
435
436     // Volume button.
437     m_volumeButton = gtk_volume_button_new();
438     gtk_box_pack_start(GTK_BOX(hbox), m_volumeButton, FALSE, TRUE, 0);
439     gtk_scale_button_set_value(GTK_SCALE_BUTTON(m_volumeButton), m_player->volume());
440     m_volumeUpdateId = g_signal_connect(m_volumeButton, "value-changed", G_CALLBACK(volumeValueChanged), this);
441
442     m_exitFullscreenAction = gtk_action_new("exit", _("Exit Fullscreen"), _("Exit from fullscreen mode"), EXIT_FULLSCREEN_ICON_NAME);
443     m_exitFullcreenActionActivateSignalId = g_signal_connect(m_exitFullscreenAction, "activate", G_CALLBACK(exitFullscreenActivated), this);
444     g_object_set(m_exitFullscreenAction, "icon-name", EXIT_FULLSCREEN_ICON_NAME, NULL);
445     item = gtk_action_create_tool_item(m_exitFullscreenAction);
446     gtk_box_pack_start(GTK_BOX(hbox), item, FALSE, TRUE, 0);
447
448     m_progressBarUpdateId = g_timeout_add(PROGRESS_BAR_UPDATE_INTERVAL, reinterpret_cast<GSourceFunc>(progressBarUpdateCallback), this);
449
450     playStateChanged();
451 }
452
453 } // namespace WebCore
454 #endif