| /* |
| * Copyright (C) 2010 Igalia S.L |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| |
| #if ENABLE(VIDEO) |
| |
| #include "FullscreenVideoController.h" |
| |
| #include "GRefPtrGtk.h" |
| #include "GtkVersioning.h" |
| #include "MediaPlayer.h" |
| |
| #include <gdk/gdk.h> |
| #include <gdk/gdkkeysyms.h> |
| #include <glib/gi18n-lib.h> |
| #include <gst/gst.h> |
| #include <gtk/gtk.h> |
| |
| using namespace std; |
| using namespace WebCore; |
| |
| #define HUD_AUTO_HIDE_INTERVAL 3000 // 3 seconds |
| #define PROGRESS_BAR_UPDATE_INTERVAL 150 // 150ms |
| #define VOLUME_UP_OFFSET 0.05 // 5% |
| #define VOLUME_DOWN_OFFSET 0.05 // 5% |
| |
| // Use symbolic icons only if we build with GTK+-3 support. They could |
| // be enabled for the GTK+2 build but we'd need to bump the required |
| // version to at least 2.22. |
| #if GTK_MAJOR_VERSION < 3 |
| #define PLAY_ICON_NAME "media-playback-start" |
| #define PAUSE_ICON_NAME "media-playback-pause" |
| #define EXIT_FULLSCREEN_ICON_NAME "view-restore" |
| #else |
| #define PLAY_ICON_NAME "media-playback-start-symbolic" |
| #define PAUSE_ICON_NAME "media-playback-pause-symbolic" |
| #define EXIT_FULLSCREEN_ICON_NAME "view-restore-symbolic" |
| #endif |
| |
| static gboolean hideHudCallback(FullscreenVideoController* controller) |
| { |
| controller->hideHud(); |
| return FALSE; |
| } |
| |
| static gboolean onFullscreenGtkMotionNotifyEvent(GtkWidget* widget, GdkEventMotion* event, FullscreenVideoController* controller) |
| { |
| controller->showHud(true); |
| return TRUE; |
| } |
| |
| static void onFullscreenGtkActiveNotification(GtkWidget* widget, GParamSpec* property, FullscreenVideoController* controller) |
| { |
| if (!gtk_window_is_active(GTK_WINDOW(widget))) |
| controller->hideHud(); |
| } |
| |
| static gboolean onFullscreenGtkConfigureEvent(GtkWidget* widget, GdkEventConfigure* event, FullscreenVideoController* controller) |
| { |
| controller->gtkConfigure(event); |
| return TRUE; |
| } |
| |
| static void onFullscreenGtkDestroy(GtkWidget* widget, FullscreenVideoController* controller) |
| { |
| controller->exitFullscreen(); |
| } |
| |
| static void togglePlayPauseActivated(GtkAction* action, FullscreenVideoController* controller) |
| { |
| controller->togglePlay(); |
| } |
| |
| static void exitFullscreenActivated(GtkAction* action, FullscreenVideoController* controller) |
| { |
| controller->exitOnUserRequest(); |
| } |
| |
| static gboolean progressBarUpdateCallback(FullscreenVideoController* controller) |
| { |
| return controller->updateHudProgressBar(); |
| } |
| |
| static gboolean timeScaleButtonPressed(GtkWidget* widget, GdkEventButton* event, FullscreenVideoController* controller) |
| { |
| if (event->type != GDK_BUTTON_PRESS) |
| return FALSE; |
| |
| controller->beginSeek(); |
| return FALSE; |
| } |
| |
| static gboolean timeScaleButtonReleased(GtkWidget* widget, GdkEventButton* event, FullscreenVideoController* controller) |
| { |
| controller->endSeek(); |
| return FALSE; |
| } |
| |
| static void timeScaleValueChanged(GtkWidget* widget, FullscreenVideoController* controller) |
| { |
| controller->doSeek(); |
| } |
| |
| static void volumeValueChanged(GtkScaleButton *button, gdouble value, FullscreenVideoController* controller) |
| { |
| controller->setVolume(static_cast<float>(value)); |
| } |
| |
| void playerVolumeChangedCallback(GObject *element, GParamSpec *pspec, FullscreenVideoController* controller) |
| { |
| controller->volumeChanged(); |
| } |
| |
| void playerMuteChangedCallback(GObject *element, GParamSpec *pspec, FullscreenVideoController* controller) |
| { |
| controller->muteChanged(); |
| } |
| |
| FullscreenVideoController::FullscreenVideoController() |
| : m_hudTimeoutId(0) |
| , m_progressBarUpdateId(0) |
| , m_seekLock(false) |
| , m_window(0) |
| , m_hudWindow(0) |
| { |
| } |
| |
| FullscreenVideoController::~FullscreenVideoController() |
| { |
| exitFullscreen(); |
| } |
| |
| void FullscreenVideoController::setMediaElement(HTMLMediaElement* mediaElement) |
| { |
| if (mediaElement == m_mediaElement) |
| return; |
| |
| m_mediaElement = mediaElement; |
| if (!m_mediaElement) { |
| // Can't do full-screen, just get out |
| exitFullscreen(); |
| } |
| } |
| |
| void FullscreenVideoController::gtkConfigure(GdkEventConfigure* event) |
| { |
| updateHudPosition(); |
| } |
| |
| void FullscreenVideoController::showHud(bool autoHide) |
| { |
| if (!m_hudWindow) |
| return; |
| |
| if (m_hudTimeoutId) { |
| g_source_remove(m_hudTimeoutId); |
| m_hudTimeoutId = 0; |
| } |
| |
| // Show the cursor. |
| GdkWindow* window = gtk_widget_get_window(m_window); |
| gdk_window_set_cursor(window, 0); |
| |
| // Update the progress bar immediately before showing the window. |
| updateHudProgressBar(); |
| gtk_widget_show_all(m_hudWindow); |
| updateHudPosition(); |
| |
| // Start periodic updates of the progress bar. |
| if (!m_progressBarUpdateId) |
| m_progressBarUpdateId = g_timeout_add(PROGRESS_BAR_UPDATE_INTERVAL, reinterpret_cast<GSourceFunc>(progressBarUpdateCallback), this); |
| |
| // Hide the hud in few seconds, if requested. |
| if (autoHide) |
| m_hudTimeoutId = g_timeout_add(HUD_AUTO_HIDE_INTERVAL, reinterpret_cast<GSourceFunc>(hideHudCallback), this); |
| } |
| |
| void FullscreenVideoController::hideHud() |
| { |
| if (m_hudTimeoutId) { |
| g_source_remove(m_hudTimeoutId); |
| m_hudTimeoutId = 0; |
| } |
| |
| if (!m_hudWindow) |
| return; |
| |
| // Keep the hud visible if a seek is in progress or if the volume |
| // popup is visible. |
| GtkWidget* volumePopup = gtk_scale_button_get_popup(GTK_SCALE_BUTTON(m_volumeButton)); |
| if (m_seekLock || gtk_widget_get_visible(volumePopup)) { |
| showHud(true); |
| return; |
| } |
| |
| GdkWindow* window = gtk_widget_get_window(m_window); |
| GdkCursor* cursor = blankCursor(); |
| gdk_window_set_cursor(window, cursor); |
| |
| gtk_widget_hide(m_hudWindow); |
| |
| if (m_progressBarUpdateId) { |
| g_source_remove(m_progressBarUpdateId); |
| m_progressBarUpdateId = 0; |
| } |
| } |
| |
| static gboolean onFullscreenGtkKeyPressEvent(GtkWidget* widget, GdkEventKey* event, FullscreenVideoController* controller) |
| { |
| switch (event->keyval) { |
| case GDK_Escape: |
| case 'f': |
| case 'F': |
| controller->exitOnUserRequest(); |
| break; |
| case GDK_space: |
| case GDK_Return: |
| controller->togglePlay(); |
| break; |
| case GDK_Up: |
| // volume up |
| controller->setVolume(controller->volume() + VOLUME_UP_OFFSET); |
| break; |
| case GDK_Down: |
| // volume down |
| controller->setVolume(controller->volume() - VOLUME_DOWN_OFFSET); |
| break; |
| default: |
| break; |
| } |
| |
| return TRUE; |
| } |
| |
| |
| void FullscreenVideoController::enterFullscreen() |
| { |
| if (!m_mediaElement) |
| return; |
| |
| if (m_mediaElement->platformMedia().type != WebCore::PlatformMedia::GStreamerGWorldType) |
| return; |
| |
| m_gstreamerGWorld = m_mediaElement->platformMedia().media.gstreamerGWorld; |
| if (!m_gstreamerGWorld->enterFullscreen()) |
| return; |
| |
| m_window = reinterpret_cast<GtkWidget*>(m_gstreamerGWorld->platformVideoWindow()->window()); |
| |
| GstElement* pipeline = m_gstreamerGWorld->pipeline(); |
| g_signal_connect(pipeline, "notify::volume", G_CALLBACK(playerVolumeChangedCallback), this); |
| g_signal_connect(pipeline, "notify::mute", G_CALLBACK(playerMuteChangedCallback), this); |
| |
| if (!m_hudWindow) |
| createHud(); |
| |
| // Ensure black background. |
| #ifdef GTK_API_VERSION_2 |
| GdkColor color = { 1, 0, 0, 0 }; |
| gtk_widget_modify_bg(m_window, GTK_STATE_NORMAL, &color); |
| #else |
| GdkRGBA color = { 0, 0, 0, 1}; |
| gtk_widget_override_background_color(m_window, GTK_STATE_FLAG_NORMAL, &color); |
| #endif |
| gtk_widget_set_double_buffered(m_window, FALSE); |
| |
| g_signal_connect(m_window, "key-press-event", G_CALLBACK(onFullscreenGtkKeyPressEvent), this); |
| g_signal_connect(m_window, "destroy", G_CALLBACK(onFullscreenGtkDestroy), this); |
| g_signal_connect(m_window, "notify::is-active", G_CALLBACK(onFullscreenGtkActiveNotification), this); |
| |
| gtk_widget_show_all(m_window); |
| |
| GdkWindow* window = gtk_widget_get_window(m_window); |
| GRefPtr<GdkCursor> cursor(adoptGRef(blankCursor())); |
| gdk_window_set_cursor(window, cursor.get()); |
| |
| g_signal_connect(m_window, "motion-notify-event", G_CALLBACK(onFullscreenGtkMotionNotifyEvent), this); |
| g_signal_connect(m_window, "configure-event", G_CALLBACK(onFullscreenGtkConfigureEvent), this); |
| |
| gtk_window_fullscreen(GTK_WINDOW(m_window)); |
| showHud(true); |
| } |
| |
| void FullscreenVideoController::updateHudPosition() |
| { |
| if (!m_hudWindow) |
| return; |
| |
| // Get the screen rectangle. |
| GdkScreen* screen = gtk_window_get_screen(GTK_WINDOW(m_window)); |
| GdkWindow* window = gtk_widget_get_window(m_window); |
| GdkRectangle fullscreenRectangle; |
| gdk_screen_get_monitor_geometry(screen, gdk_screen_get_monitor_at_window(screen, window), |
| &fullscreenRectangle); |
| |
| // Get the popup window size. |
| int hudWidth, hudHeight; |
| gtk_window_get_size(GTK_WINDOW(m_hudWindow), &hudWidth, &hudHeight); |
| |
| // Resize the hud to the full width of the screen. |
| gtk_window_resize(GTK_WINDOW(m_hudWindow), fullscreenRectangle.width, hudHeight); |
| |
| // Move the hud to the bottom of the screen. |
| gtk_window_move(GTK_WINDOW(m_hudWindow), fullscreenRectangle.x, |
| fullscreenRectangle.height + fullscreenRectangle.y - hudHeight); |
| } |
| |
| void FullscreenVideoController::exitOnUserRequest() |
| { |
| m_mediaElement->exitFullscreen(); |
| } |
| |
| void FullscreenVideoController::exitFullscreen() |
| { |
| if (!m_hudWindow) |
| return; |
| |
| g_signal_handlers_disconnect_by_func(m_window, reinterpret_cast<void*>(onFullscreenGtkKeyPressEvent), this); |
| g_signal_handlers_disconnect_by_func(m_window, reinterpret_cast<void*>(onFullscreenGtkDestroy), this); |
| g_signal_handlers_disconnect_by_func(m_window, reinterpret_cast<void*>(onFullscreenGtkMotionNotifyEvent), this); |
| g_signal_handlers_disconnect_by_func(m_window, reinterpret_cast<void*>(onFullscreenGtkConfigureEvent), this); |
| |
| GstElement* pipeline = m_mediaElement->platformMedia().media.gstreamerGWorld->pipeline(); |
| g_signal_handlers_disconnect_by_func(pipeline, reinterpret_cast<void*>(playerVolumeChangedCallback), this); |
| g_signal_handlers_disconnect_by_func(pipeline, reinterpret_cast<void*>(playerMuteChangedCallback), this); |
| |
| if (m_hudTimeoutId) { |
| g_source_remove(m_hudTimeoutId); |
| m_hudTimeoutId = 0; |
| } |
| |
| if (m_progressBarUpdateId) { |
| g_source_remove(m_progressBarUpdateId); |
| m_progressBarUpdateId = 0; |
| } |
| |
| if (m_mediaElement->platformMedia().type == WebCore::PlatformMedia::GStreamerGWorldType) |
| m_mediaElement->platformMedia().media.gstreamerGWorld->exitFullscreen(); |
| |
| gtk_widget_hide(m_window); |
| |
| gtk_widget_destroy(m_hudWindow); |
| m_hudWindow = 0; |
| } |
| |
| bool FullscreenVideoController::canPlay() const |
| { |
| return m_mediaElement && m_mediaElement->canPlay(); |
| } |
| |
| void FullscreenVideoController::play() |
| { |
| if (m_mediaElement) |
| m_mediaElement->play(m_mediaElement->processingUserGesture()); |
| |
| playStateChanged(); |
| showHud(true); |
| } |
| |
| void FullscreenVideoController::pause() |
| { |
| if (m_mediaElement) |
| m_mediaElement->pause(m_mediaElement->processingUserGesture()); |
| |
| playStateChanged(); |
| showHud(false); |
| } |
| |
| void FullscreenVideoController::playStateChanged() |
| { |
| if (canPlay()) |
| g_object_set(m_playPauseAction, "tooltip", _("Play"), "icon-name", PLAY_ICON_NAME, NULL); |
| else |
| g_object_set(m_playPauseAction, "tooltip", _("Pause"), "icon-name", PAUSE_ICON_NAME, NULL); |
| } |
| |
| void FullscreenVideoController::togglePlay() |
| { |
| if (canPlay()) |
| play(); |
| else |
| pause(); |
| } |
| |
| float FullscreenVideoController::volume() const |
| { |
| return m_mediaElement ? m_mediaElement->volume() : 0; |
| } |
| |
| bool FullscreenVideoController::muted() const |
| { |
| return m_mediaElement ? m_mediaElement->muted() : false; |
| } |
| |
| void FullscreenVideoController::setVolume(float volume) |
| { |
| if (volume < 0.0 || volume > 1.0) |
| return; |
| |
| if (m_mediaElement) { |
| ExceptionCode ec; |
| m_mediaElement->setVolume(volume, ec); |
| } |
| } |
| |
| void FullscreenVideoController::volumeChanged() |
| { |
| g_signal_handler_block(m_volumeButton, m_volumeUpdateId); |
| gtk_scale_button_set_value(GTK_SCALE_BUTTON(m_volumeButton), volume()); |
| g_signal_handler_unblock(m_volumeButton, m_volumeUpdateId); |
| } |
| |
| void FullscreenVideoController::muteChanged() |
| { |
| g_signal_handler_block(m_volumeButton, m_volumeUpdateId); |
| gtk_scale_button_set_value(GTK_SCALE_BUTTON(m_volumeButton), muted() ? 0 : volume()); |
| g_signal_handler_unblock(m_volumeButton, m_volumeUpdateId); |
| } |
| |
| float FullscreenVideoController::currentTime() const |
| { |
| return m_mediaElement ? m_mediaElement->currentTime() : 0; |
| } |
| |
| void FullscreenVideoController::setCurrentTime(float value) |
| { |
| if (m_mediaElement) { |
| ExceptionCode ec; |
| m_mediaElement->setCurrentTime(value, ec); |
| } |
| } |
| |
| float FullscreenVideoController::duration() const |
| { |
| return m_mediaElement ? m_mediaElement->duration() : 0; |
| } |
| |
| float FullscreenVideoController::percentLoaded() const |
| { |
| return m_mediaElement ? m_mediaElement->percentLoaded() : 0; |
| } |
| |
| void FullscreenVideoController::beginSeek() |
| { |
| m_seekLock = true; |
| |
| if (m_mediaElement) |
| m_mediaElement->beginScrubbing(); |
| } |
| |
| void FullscreenVideoController::doSeek() |
| { |
| if (!m_seekLock) |
| return; |
| |
| setCurrentTime(gtk_range_get_value(GTK_RANGE(m_timeHScale))*duration() / 100); |
| } |
| |
| void FullscreenVideoController::endSeek() |
| { |
| if (m_mediaElement) |
| m_mediaElement->endScrubbing(); |
| |
| m_seekLock = false; |
| } |
| |
| static String timeToString(float time) |
| { |
| if (!isfinite(time)) |
| time = 0; |
| int seconds = fabsf(time); |
| int hours = seconds / (60 * 60); |
| int minutes = (seconds / 60) % 60; |
| seconds %= 60; |
| |
| if (hours) { |
| if (hours > 9) |
| return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); |
| return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); |
| } |
| |
| return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds); |
| } |
| |
| gboolean FullscreenVideoController::updateHudProgressBar() |
| { |
| float mediaDuration(duration()); |
| float mediaPosition(currentTime()); |
| |
| if (!m_seekLock) { |
| gdouble value = 0.0; |
| |
| if (mediaPosition && mediaDuration) |
| value = (mediaPosition * 100.0) / mediaDuration; |
| |
| GtkAdjustment* adjustment = gtk_range_get_adjustment(GTK_RANGE(m_timeHScale)); |
| gtk_adjustment_set_value(adjustment, value); |
| } |
| |
| gtk_range_set_fill_level(GTK_RANGE(m_timeHScale), percentLoaded()* 100); |
| |
| gchar* label = g_strdup_printf("%s / %s", timeToString(mediaPosition).utf8().data(), |
| timeToString(mediaDuration).utf8().data()); |
| gtk_label_set_text(GTK_LABEL(m_timeLabel), label); |
| g_free(label); |
| return TRUE; |
| } |
| |
| void FullscreenVideoController::createHud() |
| { |
| m_hudWindow = gtk_window_new(GTK_WINDOW_POPUP); |
| gtk_window_set_gravity(GTK_WINDOW(m_hudWindow), GDK_GRAVITY_SOUTH_WEST); |
| gtk_window_set_type_hint(GTK_WINDOW(m_hudWindow), GDK_WINDOW_TYPE_HINT_NORMAL); |
| |
| g_signal_connect(m_hudWindow, "motion-notify-event", G_CALLBACK(onFullscreenGtkMotionNotifyEvent), this); |
| |
| GtkWidget* hbox = gtk_hbox_new(FALSE, 4); |
| gtk_container_add(GTK_CONTAINER(m_hudWindow), hbox); |
| |
| m_playPauseAction = gtk_action_new("play", _("Play / Pause"), _("Play or pause the media"), PAUSE_ICON_NAME); |
| g_signal_connect(m_playPauseAction, "activate", G_CALLBACK(togglePlayPauseActivated), this); |
| |
| playStateChanged(); |
| |
| GtkWidget* item = gtk_action_create_tool_item(m_playPauseAction); |
| gtk_box_pack_start(GTK_BOX(hbox), item, FALSE, TRUE, 0); |
| |
| GtkWidget* label = gtk_label_new(_("Time:")); |
| gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0); |
| |
| GtkAdjustment* adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 100.0, 0.1, 1.0, 1.0)); |
| m_timeHScale = gtk_hscale_new(adjustment); |
| gtk_scale_set_draw_value(GTK_SCALE(m_timeHScale), FALSE); |
| gtk_range_set_show_fill_level(GTK_RANGE(m_timeHScale), TRUE); |
| g_signal_connect(m_timeHScale, "button-press-event", G_CALLBACK(timeScaleButtonPressed), this); |
| g_signal_connect(m_timeHScale, "button-release-event", G_CALLBACK(timeScaleButtonReleased), this); |
| m_hscaleUpdateId = g_signal_connect(m_timeHScale, "value-changed", G_CALLBACK(timeScaleValueChanged), this); |
| |
| gtk_box_pack_start(GTK_BOX(hbox), m_timeHScale, TRUE, TRUE, 0); |
| |
| m_timeLabel = gtk_label_new(""); |
| gtk_box_pack_start(GTK_BOX(hbox), m_timeLabel, FALSE, TRUE, 0); |
| |
| // Volume button. |
| m_volumeButton = gtk_volume_button_new(); |
| gtk_box_pack_start(GTK_BOX(hbox), m_volumeButton, FALSE, TRUE, 0); |
| gtk_scale_button_set_value(GTK_SCALE_BUTTON(m_volumeButton), volume()); |
| m_volumeUpdateId = g_signal_connect(m_volumeButton, "value-changed", G_CALLBACK(volumeValueChanged), this); |
| |
| |
| m_exitFullscreenAction = gtk_action_new("exit", _("Exit Fullscreen"), _("Exit from fullscreen mode"), EXIT_FULLSCREEN_ICON_NAME); |
| g_signal_connect(m_exitFullscreenAction, "activate", G_CALLBACK(exitFullscreenActivated), this); |
| g_object_set(m_exitFullscreenAction, "icon-name", EXIT_FULLSCREEN_ICON_NAME, NULL); |
| item = gtk_action_create_tool_item(m_exitFullscreenAction); |
| gtk_box_pack_start(GTK_BOX(hbox), item, FALSE, TRUE, 0); |
| |
| |
| m_progressBarUpdateId = g_timeout_add(PROGRESS_BAR_UPDATE_INTERVAL, reinterpret_cast<GSourceFunc>(progressBarUpdateCallback), this); |
| } |
| |
| #endif |