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 55namespace WebCore { 56 57static gboolean hideHudCallback(FullscreenVideoControllerGtk* controller) 58{ 59 controller->hideHud(); 60 return FALSE; 61} 62 63static gboolean onFullscreenGtkMotionNotifyEvent(GtkWidget* widget, GdkEventMotion* event, FullscreenVideoControllerGtk* controller) 64{ 65 controller->showHud(true); 66 return TRUE; 67} 68 69static void onFullscreenGtkActiveNotification(GtkWidget* widget, GParamSpec* property, FullscreenVideoControllerGtk* controller) 70{ 71 if (!gtk_window_is_active(GTK_WINDOW(widget))) 72 controller->hideHud(); 73} 74 75static gboolean onFullscreenGtkConfigureEvent(GtkWidget* widget, GdkEventConfigure* event, FullscreenVideoControllerGtk* controller) 76{ 77 controller->gtkConfigure(event); 78 return TRUE; 79} 80 81static void onFullscreenGtkDestroy(GtkWidget* widget, FullscreenVideoControllerGtk* controller) 82{ 83 controller->exitFullscreen(); 84} 85 86static void togglePlayPauseActivated(GtkAction* action, FullscreenVideoControllerGtk* controller) 87{ 88 controller->togglePlay(); 89} 90 91static void exitFullscreenActivated(GtkAction* action, FullscreenVideoControllerGtk* controller) 92{ 93 controller->exitOnUserRequest(); 94} 95 96static gboolean progressBarUpdateCallback(FullscreenVideoControllerGtk* controller) 97{ 98 return controller->updateHudProgressBar(); 99} 100 101static 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 110static gboolean timeScaleButtonReleased(GtkWidget* widget, GdkEventButton* event, FullscreenVideoControllerGtk* controller) 111{ 112 controller->endSeek(); 113 return FALSE; 114} 115 116static void timeScaleValueChanged(GtkWidget* widget, FullscreenVideoControllerGtk* controller) 117{ 118 controller->doSeek(); 119} 120 121static void volumeValueChanged(GtkScaleButton *button, gdouble value, FullscreenVideoControllerGtk* controller) 122{ 123 controller->setVolume(static_cast<float>(value)); 124} 125 126 127FullscreenVideoControllerGtk::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 148void FullscreenVideoControllerGtk::gtkConfigure(GdkEventConfigure* event) 149{ 150 updateHudPosition(); 151} 152 153void 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 181void 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 211static 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 234void 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 268void 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 290void 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 325void 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 334void 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 344void 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 354void FullscreenVideoControllerGtk::beginSeek() 355{ 356 m_seekLock = true; 357} 358 359void 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 367void FullscreenVideoControllerGtk::endSeek() 368{ 369 m_seekLock = false; 370} 371 372gboolean 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 395void 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 455