 data/totem.ui                             |   34 ++++++++++
 src/backend/bacon-video-widget-gst-0.10.c |   95 +++++++++++++++++++++++++++-
 src/totem-menu.c                          |   21 ++++++
 src/totem-object.c                        |   26 ++++++++
 4 files changed, 172 insertions(+), 4 deletions(-)

diff --git a/data/totem.ui b/data/totem.ui
index 8255071..253032d 100644
--- a/data/totem.ui
+++ b/data/totem.ui
@@ -403,6 +403,37 @@
       </object>
    </child>
    <child>
+      <object class="GtkActionGroup" id="playbackrate-action-group">
+         <child>
+            <object class="GtkAction" id="playback-rate-increase">
+               <property name="label" translatable="yes">Playback Rate Increase</property>
+               <property name="stock-id">gtk-pbrate-increase</property>
+               <property name="tooltip" translatable="yes">Playback Rate Increase</property>
+               <signal name="activate" handler="pbrate_increase_action_callback"/>
+            </object>
+            <accelerator key="Page_Up"/>
+         </child>
+         <child>
+            <object class="GtkAction" id="playback-rate-decrease">
+               <property name="label" translatable="yes">Playback Rate Decrease</property>
+               <property name="stock-id">gtk-pbrate-decrease</property>
+               <property name="tooltip" translatable="yes">Playback Rate Decrease</property>
+               <signal name="activate" handler="pbrate_decrease_action_callback"/>
+            </object>
+            <accelerator key="Page_Down"/>
+         </child>
+         <child>
+            <object class="GtkAction" id="playback-rate-reset">
+               <property name="label" translatable="yes">Playback Rate Reset</property>
+               <property name="stock-id">gtk-pbrate-reset</property>
+               <property name="tooltip" translatable="yes">Playback Rate Reset</property>
+               <signal name="activate" handler="pbrate_reset_action_callback"/>
+            </object>
+            <accelerator key="Backslash"/>
+         </child>
+      </object>
+   </child>
+   <child>
       <object class="GtkActionGroup" id="skip-action-group">
          <child>
             <object class="GtkAction" id="skip-forward">
@@ -469,6 +500,9 @@
                <menuitem name="aspect-ratio-anamorphic" action="aspect-ratio-anamorphic"/>
                <menuitem name="aspect-ratio-dvb" action="aspect-ratio-dvb"/>
             </menu>
+            <menuitem name="playback-rate-increase" action="playback-rate-increase"/>
+            <menuitem name="playback-rate-decrease" action="playback-rate-decrease"/>
+            <menuitem name="playback-rate-reset" action="playback-rate-reset"/>
             <menuitem name="deinterlace" action="deinterlace"/>
             <menuitem name="next-angle" action="next-angle"/>
 	    <menuitem name="show-controls" action="show-controls"/>
diff --git a/src/backend/bacon-video-widget-gst-0.10.c b/src/backend/bacon-video-widget-gst-0.10.c
index 020aacc..5a0d921 100644
--- a/src/backend/bacon-video-widget-gst-0.10.c
+++ b/src/backend/bacon-video-widget-gst-0.10.c
@@ -206,6 +206,7 @@ struct BaconVideoWidgetPrivate
   BvwVisualsQuality            visq;
   gchar                       *vis_element_name;
   GstElement                  *audio_capsfilter;
+  GstElement                  *audio_pitchcontrol;
 
   /* Other stuff */
   gboolean                     logo_mode;
@@ -307,6 +308,10 @@ static gboolean bvw_check_for_cover_pixbuf (BaconVideoWidget * bvw);
 static const GdkPixbuf * bvw_get_logo_pixbuf (BaconVideoWidget * bvw);
 static gboolean bvw_set_playback_direction (BaconVideoWidget *bvw, gboolean forward);
 static gboolean bacon_video_widget_seek_time_no_lock (BaconVideoWidget *bvw, gint64 _time, GError **error);
+gboolean bvw_change_rate (BaconVideoWidget *bvw, gboolean increase);
+gboolean bvw_set_rate (BaconVideoWidget *bvw, gfloat new_rate);
+gboolean bvw_reset_rate (BaconVideoWidget *bvw);
+gfloat bvw_get_rate (BaconVideoWidget *bvw);
 
 typedef struct {
   GstTagList *tags;
@@ -3947,8 +3952,9 @@ bacon_video_widget_seek_time_no_lock (BaconVideoWidget *bvw, gint64 _time, GErro
 
   bvw->priv->seek_time = -1;
   bvw->priv->rate = FORWARD_RATE;
+  g_object_set (bvw->priv->audio_pitchcontrol, "pitch", 1.0, NULL);
 
-  gst_element_seek (bvw->priv->play, FORWARD_RATE,
+  gst_element_seek (bvw->priv->play, bvw->priv->rate,
       GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
       GST_SEEK_TYPE_SET, _time * GST_MSECOND,
       GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
@@ -4179,10 +4185,10 @@ bacon_video_widget_close (BaconVideoWidget * bvw)
   bvw->priv->user_id = NULL;
   g_free (bvw->priv->user_pw);
   bvw->priv->user_pw = NULL;
-
   bvw->priv->is_live = FALSE;
   bvw->priv->window_resized = FALSE;
   bvw->priv->rate = FORWARD_RATE;
+  g_object_set (bvw->priv->audio_pitchcontrol, "pitch", 1.0, NULL);
 
   bvw->priv->seek_req_time = GST_CLOCK_TIME_NONE;
   bvw->priv->seek_time = -1;
@@ -4305,6 +4311,7 @@ bacon_video_widget_dvd_event (BaconVideoWidget * bvw,
         gst_element_seek (bvw->priv->play, FORWARD_RATE, fmt, GST_SEEK_FLAG_FLUSH,
             GST_SEEK_TYPE_SET, val, GST_SEEK_TYPE_NONE, 0);
 	bvw->priv->rate = FORWARD_RATE;
+  	g_object_set (bvw->priv->audio_pitchcontrol, "pitch", 1.0, NULL);
       } else {
         GST_DEBUG ("failed to query position (%s)", fmt_name);
       }
@@ -6680,6 +6687,7 @@ bvw_set_playback_direction (BaconVideoWidget *bvw, gboolean forward)
       } else {
 	gst_element_get_state (bvw->priv->play, NULL, NULL, GST_CLOCK_TIME_NONE);
 	bvw->priv->rate = REVERSE_RATE;
+  	g_object_set (bvw->priv->audio_pitchcontrol, "pitch", 1.0, NULL);
 	retval = TRUE;
       }
     } else {
@@ -6703,6 +6711,7 @@ bvw_set_playback_direction (BaconVideoWidget *bvw, gboolean forward)
       } else {
 	gst_element_get_state (bvw->priv->play, NULL, NULL, GST_CLOCK_TIME_NONE);
 	bvw->priv->rate = FORWARD_RATE;
+  	g_object_set (bvw->priv->audio_pitchcontrol, "pitch", 1.0, NULL);
 	retval = TRUE;
       }
     } else {
@@ -7002,7 +7011,6 @@ bacon_video_widget_new (int width, int height,
   /* set back to NULL to close device again in order to avoid interrupts
    * being generated after startup while there's nothing to play yet */
   gst_element_set_state (audio_sink, GST_STATE_NULL);
-
   do {
     GstElement *bin;
     GstPad *pad;
@@ -7010,9 +7018,16 @@ bacon_video_widget_new (int width, int height,
     bvw->priv->audio_capsfilter =
         gst_element_factory_make ("capsfilter", "audiofilter");
     bin = gst_bin_new ("audiosinkbin");
+
+    bvw->priv->audio_pitchcontrol =
+        gst_element_factory_make ("pitch", "audiopitch");
+    g_object_set (bvw->priv->audio_pitchcontrol, "pitch", 1.0, NULL);
+
     gst_bin_add_many (GST_BIN (bin), bvw->priv->audio_capsfilter,
-        audio_sink, NULL);
+        bvw->priv->audio_pitchcontrol, audio_sink, NULL);
     gst_element_link_pads (bvw->priv->audio_capsfilter, "src",
+        bvw->priv->audio_pitchcontrol, "sink");
+    gst_element_link_pads (bvw->priv->audio_pitchcontrol, "src",
         audio_sink, "sink");
 
     pad = gst_element_get_static_pad (bvw->priv->audio_capsfilter, "sink");
@@ -7144,6 +7159,78 @@ sink_error:
   }
 }
 
+gfloat
+bvw_get_rate (BaconVideoWidget *bvw)
+{
+      return bvw->priv->rate;
+}
+
+gboolean
+bvw_set_rate (BaconVideoWidget *bvw, gfloat new_rate)
+{
+  GstEvent *event;
+  gboolean retval = FALSE;
+  GstFormat fmt;
+  gint64 cur;
+  gfloat pitch, ratio;
+
+  g_return_val_if_fail (bvw != NULL, FALSE);
+  g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+  g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+  /* set upper and lower limit for rate */
+  if (new_rate <= 0.5)
+	return TRUE;
+  if (new_rate >= 2.0)
+	return TRUE;
+  ratio = new_rate / bvw->priv->rate;
+
+  fmt = GST_FORMAT_TIME;
+
+  if (gst_element_query_position (bvw->priv->play, &fmt, &cur)) {
+    GST_DEBUG ("Setting playback direction to reverse at %"G_GINT64_FORMAT"", cur);
+    event = gst_event_new_seek (new_rate,
+        fmt, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
+        GST_SEEK_TYPE_SET, cur,
+	GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE);
+    if (gst_element_send_event (bvw->priv->play, event) == FALSE) {
+      GST_DEBUG ("Failed to change rate");
+    } else {
+      gst_element_get_state (bvw->priv->play, NULL, NULL, GST_CLOCK_TIME_NONE);
+      bvw->priv->rate = new_rate;
+      g_object_get (bvw->priv->audio_pitchcontrol, "pitch", &pitch, NULL);
+      g_object_set (bvw->priv->audio_pitchcontrol, "pitch", pitch/ratio, NULL);
+      GST_DEBUG ("changed rate to %f, pitch to %f\n", new_rate, pitch);
+      retval = TRUE;
+    }
+  }
+  else {
+    GST_DEBUG ("failed to query position");
+  }
+  return retval;
+}
+
+gboolean
+bvw_change_rate (BaconVideoWidget *bvw, gboolean increase)
+{
+  gfloat rate;
+
+  g_return_val_if_fail (bvw != NULL, FALSE);
+  g_return_val_if_fail (BACON_IS_VIDEO_WIDGET (bvw), FALSE);
+  g_return_val_if_fail (GST_IS_ELEMENT (bvw->priv->play), FALSE);
+
+  rate = bvw->priv->rate;
+  rate += (increase) ? 0.1 : -0.1;
+
+  return bvw_set_rate(bvw, rate);
+}
+
+gboolean
+bvw_reset_rate(BaconVideoWidget *bvw)
+{
+  return bvw_set_rate(bvw, 1.0);
+}
+
 /*
  * vim: sw=2 ts=8 cindent noai bs=2
  */
diff --git a/src/totem-menu.c b/src/totem-menu.c
index 5d977cd..4a13817 100644
--- a/src/totem-menu.c
+++ b/src/totem-menu.c
@@ -79,6 +79,9 @@ G_MODULE_EXPORT void show_sidebar_action_callback (GtkToggleAction *action, Tote
 G_MODULE_EXPORT void aspect_ratio_changed_callback (GtkRadioAction *action, GtkRadioAction *current, Totem *totem);
 G_MODULE_EXPORT void select_subtitle_action_callback (GtkAction *action, Totem *totem);
 G_MODULE_EXPORT void clear_playlist_action_callback (GtkAction *action, Totem *totem);
+G_MODULE_EXPORT void pbrate_increase_action_callback (GtkAction *action, Totem *totem);
+G_MODULE_EXPORT void pbrate_decrease_action_callback (GtkAction *action, Totem *totem);
+G_MODULE_EXPORT void pbrate_reset_action_callback (GtkAction *action, Totem *totem);
 
 /* Helper function to escape underscores in labels
  * before putting them in menu items */
@@ -1057,6 +1060,24 @@ zoom_in_action_callback (GtkAction *action, Totem *totem)
 }
 
 void
+pbrate_increase_action_callback (GtkAction *action, Totem *totem)
+{
+	totem_action_pbrate_change (totem, TRUE);
+}
+
+void
+pbrate_decrease_action_callback (GtkAction *action, Totem *totem)
+{
+	totem_action_pbrate_change (totem, FALSE);
+}
+
+void
+pbrate_reset_action_callback (GtkAction *action, Totem *totem)
+{
+	totem_action_pbrate_reset (totem);
+}
+
+void
 zoom_reset_action_callback (GtkAction *action, Totem *totem)
 {
 	totem_action_zoom_reset (totem);
diff --git a/src/totem-object.c b/src/totem-object.c
index 0a55903..5dfb7a1 100644
--- a/src/totem-object.c
+++ b/src/totem-object.c
@@ -103,6 +103,8 @@ static void update_fill (Totem *totem, gdouble level);
 static void update_media_menu_items (Totem *totem);
 static void playlist_changed_cb (GtkWidget *playlist, Totem *totem);
 static void play_pause_set_label (Totem *totem, TotemStates state);
+void totem_action_pbrate_change (Totem *totem, gboolean increase);
+void totem_action_pbrate_reset (Totem *totem);
 
 /* Callback functions for GtkBuilder */
 G_MODULE_EXPORT gboolean main_window_destroy_cb (GtkWidget *widget, GdkEvent *event, Totem *totem);
@@ -1997,6 +1999,18 @@ totem_action_zoom_reset (Totem *totem)
 	totem_action_zoom (totem, ZOOM_RESET);
 }
 
+void
+totem_action_pbrate_change (Totem *totem, gboolean increase)
+{
+	bvw_change_rate (totem->bvw, increase);
+}
+
+void
+totem_action_pbrate_reset (Totem *totem)
+{
+	bvw_reset_rate (totem->bvw);
+}
+
 /**
  * totem_action_get_volume:
  * @totem: a #TotemObject
@@ -3719,6 +3733,18 @@ totem_action_handle_key_press (Totem *totem, GdkEventKey *event)
 	case GDK_KP_5:
 		bacon_video_widget_dvd_event (totem->bvw,
 					      BVW_DVD_ROOT_MENU_SELECT);
+    case GDK_KP_9:
+    case GDK_Page_Up:
+        bvw_change_rate (totem->bvw, TRUE);
+        break;
+    case GDK_KP_3:
+    case GDK_Page_Down:
+        bvw_change_rate (totem->bvw, FALSE);
+        break;
+    case GDK_backslash:
+        bvw_reset_rate (totem->bvw);
+        break;
+
 	default:
 		retval = FALSE;
 	}
