diff -Naur -x .svn gst-python-orig/gst/arg-types.py gst-python/gst/arg-types.py
--- gst-python-orig/gst/arg-types.py	2006-12-14 19:48:06.000000000 +0100
+++ gst-python/gst/arg-types.py	2006-12-14 19:47:03.000000000 +0100
@@ -320,9 +320,6 @@
 matcher.register('GstCaps*', GstCapsArg()) #FIXME: does this work?
 matcher.register('const-GstCaps*', GstCapsArg())
 
-arg = PointerArg('gpointer', 'G_TYPE_POINTER')
-matcher.register('GstClockID', arg)
-
 for typename in ["GstPlugin", "GstStructure", "GstTagList", "GError", "GstDate", "GstSegment"]:
 	matcher.register_reverse(typename, GBoxedParam)
 	matcher.register_reverse_ret(typename, GBoxedReturn)
@@ -346,5 +343,3 @@
 
 matcher.register_reverse("gulong", ULongParam)
 matcher.register_reverse_ret("gulong", ULongReturn)
-
-del arg
diff -Naur -x .svn gst-python-orig/gst/common.h gst-python/gst/common.h
--- gst-python-orig/gst/common.h	2006-12-14 19:48:06.000000000 +0100
+++ gst-python/gst/common.h	2006-12-14 19:47:03.000000000 +0100
@@ -50,6 +50,14 @@
 
 typedef struct {
     PyObject_HEAD
+    GstClockID clock_id;
+    PyObject *async_callback;
+} PyGstClockID;
+
+PyTypeObject PyGstClockID_Type;
+
+typedef struct {
+    PyObject_HEAD
     GstIterator *iter;
 } PyGstIterator;
 
@@ -58,6 +66,7 @@
 
 /* from gst-types.c */
 GstCaps *pygst_caps_from_pyobject (PyObject *object, gboolean *copy);
+PyObject* pygst_clock_id_new(GstClockID id);
 PyObject* pygst_iterator_new(GstIterator *iter);
 
 
diff -Naur -x .svn gst-python-orig/gst/gstmodule.c gst-python/gst/gstmodule.c
--- gst-python-orig/gst/gstmodule.c	2006-12-14 19:48:06.000000000 +0100
+++ gst-python/gst/gstmodule.c	2006-12-14 19:47:03.000000000 +0100
@@ -188,6 +188,9 @@
 
      pygst_exceptions_register_classes (d);
      
+     if (PyType_Ready(&PyGstClockID_Type))
+	  return;
+     PyDict_SetItemString(d, "ClockID", (PyObject *)&PyGstClockID_Type);
      REGISTER_TYPE(d, PyGstIterator_Type, "Iterator");
 
 
diff -Naur -x .svn gst-python-orig/gst/gst.override gst-python/gst/gst.override
--- gst-python-orig/gst/gst.override	2006-12-14 19:48:06.000000000 +0100
+++ gst-python/gst/gst.override	2006-12-14 19:47:03.000000000 +0100
@@ -927,6 +927,46 @@
     return ret;
 }
 %%
+override gst_clock_new_single_shot_id kwargs
+static PyObject *
+_wrap_gst_clock_new_single_shot_id(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "time", NULL };
+    GstClockID ret;
+    guint64 time;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "K:GstClock.new_single_shot_id", kwlist, &time))
+        return NULL;
+
+    ret = gst_clock_new_single_shot_id(GST_CLOCK(self->obj), time);
+    
+    return pygst_clock_id_new(ret);
+}
+%%
+override gst_clock_new_periodic_id kwargs
+static PyObject *
+_wrap_gst_clock_new_periodic_id(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+    static char *kwlist[] = { "start_time", "interval", NULL };
+    GstClockID ret;
+    guint64 start_time, interval;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "KK:GstClock.new_periodic_id", kwlist, &start_time, &interval))
+        return NULL;
+    if (!GST_CLOCK_TIME_IS_VALID (start_time)) {
+	PyErr_SetString(PyExc_ValueError, "Bad time value");
+	return NULL;
+    }
+    if (interval == 0 || !GST_CLOCK_TIME_IS_VALID (interval)) {
+	PyErr_SetString(PyExc_ValueError, "Bad interval time value");
+	return NULL;
+    }
+
+    ret = gst_clock_new_periodic_id(GST_CLOCK(self->obj), start_time, interval);
+    
+    return pygst_clock_id_new(ret);
+}
+%%
 override gst_type_find_helper_for_buffer kwargs
 static PyObject *
 _wrap_gst_type_find_helper_for_buffer (PyObject *self, PyObject *args, PyObject *kwargs)
diff -Naur -x .svn gst-python-orig/gst/Makefile.am gst-python/gst/Makefile.am
--- gst-python-orig/gst/Makefile.am	2006-12-14 19:48:06.000000000 +0100
+++ gst-python/gst/Makefile.am	2006-12-14 19:47:03.000000000 +0100
@@ -43,6 +43,7 @@
 _gst_la_SOURCES = 		\
 	gst-argtypes.c 		\
 	gstmodule.c 		\
+	pygstclockid.c		\
 	pygstiterator.c		\
 	pygstminiobject.c 	\
 	pygstvalue.c 		\
diff -Naur -x .svn gst-python-orig/gst/pygstclockid.c gst-python/gst/pygstclockid.c
--- gst-python-orig/gst/pygstclockid.c	1970-01-01 01:00:00.000000000 +0100
+++ gst-python/gst/pygstclockid.c	2006-12-14 19:47:03.000000000 +0100
@@ -0,0 +1,341 @@
+/* -*- Mode: C; c-basic-offset: 4 -*- */
+/* gst-python
+ * Copyright (C) 2006 Rene Stadler <mail@renestadler.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301  USA
+ *
+ * Author: Rene Stadler <mail@renestadler.de>
+ */
+
+#include "common.h"
+#include "pygstexception.h"
+
+/* Translate erroneous clock return values from gst_clock_id_wait and
+ * _wait_async into proper exceptions. */
+static void
+set_exception_from_clock_return (GstClockReturn ret)
+{
+  switch (ret) {
+  case GST_CLOCK_OK:
+  case GST_CLOCK_EARLY:
+    g_return_if_reached ();
+    break;
+  case GST_CLOCK_UNSCHEDULED:
+    /* id got .unschedule ().  This is especially useful for aborting the
+     * blocking .wait call, hence a separate exception for it. */
+    PyErr_SetString (PyGstExc_ClockIDUnscheduled, "ID got unscheduled");
+    break;
+  case GST_CLOCK_BUSY:
+    /* We should never get this, it is internal to clock implementations. */
+    PyErr_SetString (PyGstExc_ClockError, "ID is busy");
+    break;
+  case GST_CLOCK_BADTIME:
+    PyErr_SetString (PyExc_ValueError, "Bad time value");
+    break;
+  case GST_CLOCK_UNSUPPORTED:
+    /* The clock's capabilities are too limited. */
+    PyErr_SetString (PyGstExc_ClockError, "Operation not supported");
+    break;    
+  case GST_CLOCK_ERROR:
+    PyErr_SetString (PyExc_RuntimeError, "Internal clock error");
+    break;    
+  default:
+    PyErr_Format (PyExc_RuntimeError, "Unknown clock return: %i", (int) ret);
+    break;
+  }
+}
+
+/* Callback we pass to gst_clock_id_wait_async. */
+static gboolean
+pygst_clock_id_async_callback (GstClock *clock, GstClockTime time,
+    GstClockID clock_id, PyGstClockID * self)
+{
+  PyGILState_STATE state;
+  PyObject *clock_pyobj;
+  PyObject *callback_args, *user_data, *all_args;
+  PyObject *result;
+  gboolean ret = FALSE;
+
+  g_return_val_if_fail (self != NULL, FALSE);
+
+  state = pyg_gil_state_ensure ();
+
+  /* This check avoids a possible race during disposal. */
+  if (G_LIKELY (self->async_callback != NULL)) {
+    
+    clock_pyobj = pygobject_new (G_OBJECT (clock));
+    callback_args = Py_BuildValue ("OKO", clock_pyobj, time, self);
+    user_data = PyTuple_GetSlice (self->async_callback, 1,
+	PyTuple_Size (self->async_callback));
+    all_args = PySequence_Concat (callback_args, user_data);
+
+    result = PyObject_Call (PyTuple_GetItem (self->async_callback, 0),
+	all_args, NULL);
+    
+    Py_DECREF (clock_pyobj);
+    Py_DECREF (callback_args);
+    Py_DECREF (user_data);
+    Py_DECREF (all_args);
+    
+    if (result == NULL)
+      PyErr_Print ();
+    else
+      ret = PyObject_IsTrue (result);
+
+    Py_XDECREF (result);
+  }
+
+  pyg_gil_state_release (state);
+
+  return ret;
+}
+
+static int
+pygst_clock_id_compare (PyGstClockID * self, PyGstClockID * other)
+{
+  return gst_clock_id_compare_func (self->clock_id, other->clock_id);
+}
+
+static PyObject *
+pygst_clock_id_repr (PyGstClockID * self)
+{   
+  return PyString_FromFormat ("<%s (for %p) at %p>",
+      self->ob_type->tp_name, (void *) self->clock_id, (void *) self);
+}
+
+static long
+pygst_clock_id_hash (PyGstClockID * self)
+{
+  return (long) self->clock_id;
+}
+
+static int
+pygst_clock_id_traverse (PyGstClockID * self, visitproc visit, void * arg)
+{
+  /* Use this once we depend on Python 2.4:
+   *  Py_VISIT (self->async_callback);
+   */
+
+  int ret;
+
+  if (self->async_callback != NULL) {
+    ret = visit (self->async_callback, arg);
+    if (ret != 0)
+      return ret;
+  }
+  
+  return 0;
+}
+
+static int
+pygst_clock_id_clear (PyGstClockID * self)
+{
+  /* Use this once we depend on Python 2.4:
+   *  Py_CLEAR (self->async_callback);
+   */
+
+  PyObject *tmp;
+
+  tmp = self->async_callback;
+  self->async_callback = NULL;
+  Py_XDECREF (tmp);
+
+  return 0;
+}
+
+/* Type method implementation for __new__.  It does _not_ create a new ClockID
+ * instance, because an object created this way would be useless. */
+PyObject *
+pygst_clock_id__new__ (PyTypeObject *subtype, PyObject *args, PyObject *kwds)
+{
+  PyErr_SetString (PyExc_NotImplementedError, "use new_single_shot_id and "
+      "new_periodic_id methods of gst.Clock objects instead");
+  return NULL;
+}
+
+/* Used by the wrappers for new_single_shot_id and new_periodic_id methods of
+ * Clock objects, which serve as factory functions for ClockID objects. */
+PyObject *
+pygst_clock_id_new (GstClockID clock_id)
+{
+  PyGstClockID *self;
+
+  if (clock_id == NULL) {
+    PyErr_SetString (PyExc_RuntimeError, "cannot create ClockID object");
+    return NULL;
+  }
+
+  self = PyObject_GC_New (PyGstClockID, &PyGstClockID_Type);
+
+  self->clock_id = clock_id;
+  self->async_callback = NULL;
+
+  PyObject_GC_Track ((PyObject *) self);
+
+  return (PyObject *) self;
+}
+
+static void
+pygst_clock_id_dealloc (PyGstClockID * self)
+{
+  /* Abort any outstanding async notification: */
+  gst_clock_id_unschedule (self->clock_id);
+
+  PyObject_GC_UnTrack ((PyObject *) self);
+  pygst_clock_id_clear (self);
+  gst_clock_id_unref (self->clock_id);
+
+  PyObject_GC_Del ((PyObject *) self);
+}
+
+/* Method implementations. */
+
+static PyObject *
+pygst_clock_id_get_time (PyGstClockID * self)
+{
+  GstClockTime time;
+
+  time = gst_clock_id_get_time (self->clock_id);
+
+  return PyLong_FromUnsignedLongLong (time);
+}
+
+static PyObject *
+pygst_clock_id_wait (PyGstClockID * self)
+{
+  GstClockTimeDiff jitter;
+  GstClockReturn ret;
+
+  pyg_begin_allow_threads;
+  
+  ret = gst_clock_id_wait (self->clock_id, &jitter);
+  
+  pyg_end_allow_threads;
+
+  switch (ret) {
+  case GST_CLOCK_OK:
+  case GST_CLOCK_EARLY:
+    return PyLong_FromLongLong (jitter);
+  default:
+    set_exception_from_clock_return (ret);
+    return NULL;
+  }
+}
+
+static PyObject *
+pygst_clock_id_wait_async (PyGstClockID * self, PyObject * args)
+{
+  PyObject *tmp;
+  int count;
+  GstClockReturn ret;
+
+  count = PyTuple_Size (args);
+
+  if (count == 0) {
+    PyErr_SetString (PyExc_TypeError,
+	"wait_async takes at least one argument (0 given)");
+    return NULL;
+  }
+
+  if (!PyCallable_Check (PyTuple_GetItem (args, 0))) {
+    PyErr_Format (PyExc_TypeError, "'%s' object is not callable",
+	PyTuple_GetItem (args, 0)->ob_type->tp_name);
+    return NULL;
+  }
+
+  /* Use Py_CLEAR once we depend on Python 2.4 */
+  tmp = self->async_callback;
+  self->async_callback = NULL;
+  Py_XDECREF (tmp);
+
+  self->async_callback = args;
+  Py_INCREF (self->async_callback);
+
+  ret = gst_clock_id_wait_async (self->clock_id,
+      (GstClockCallback) pygst_clock_id_async_callback, self);
+
+  switch (ret) {
+  case GST_CLOCK_OK:
+  case GST_CLOCK_EARLY:
+    Py_INCREF (Py_None);
+    return Py_None;
+  default:
+    set_exception_from_clock_return (ret);
+    return NULL;
+  }
+}
+
+static PyObject *
+pygst_clock_id_unschedule (PyGstClockID * self)
+{
+  gst_clock_id_unschedule (self->clock_id);
+
+  Py_INCREF (Py_None);
+  return Py_None;
+}
+
+static PyMethodDef _PyGstClockID_methods[] = {
+  {"get_time", (PyCFunction) pygst_clock_id_get_time, METH_NOARGS},
+  {"wait", (PyCFunction) pygst_clock_id_wait, METH_NOARGS},
+  {"wait_async", (PyCFunction) pygst_clock_id_wait_async, METH_VARARGS,
+   "clock_id.wait_async(callback, *user_data) -- once time is reached, invoke\n"
+   "callback with arguments (clock, time, clock_id, *user_data) from the\n"
+   "clock's async thread."},
+  {"unschedule", (PyCFunction) pygst_clock_id_unschedule, METH_NOARGS},
+  {NULL, NULL, 0}
+};
+
+PyTypeObject PyGstClockID_Type = {
+  PyObject_HEAD_INIT (NULL)
+  0,                            /* ob_size */
+  "gst.ClockID",                /* tp_name */
+  sizeof (PyGstClockID),        /* tp_basicsize */
+  0,                            /* tp_itemsize */
+  (destructor) pygst_clock_id_dealloc,  /* tp_dealloc */
+  0,                            /* tp_print */
+  0,                            /* tp_getattr */
+  0,                            /* tp_setattr */
+  (cmpfunc) pygst_clock_id_compare,  /* tp_compare */
+  (reprfunc) pygst_clock_id_repr,  /* tp_repr */
+  0,                            /* tp_as_number */
+  0,                            /* tp_as_sequence */
+  0,                            /* tp_as_mapping */
+  (hashfunc) pygst_clock_id_hash,  /* tp_hash */
+  0,                            /* tp_call */
+  0,                            /* tp_str */
+  0,                            /* tp_getattro */
+  0,                            /* tp_setattro */
+  0,                            /* tp_as_buffer */
+  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,  /* tp_flags */
+  "Object GstClockID",          /* tp_doc */
+  (traverseproc) pygst_clock_id_traverse,  /* tp_traverse */
+  (inquiry) pygst_clock_id_clear,  /* tp_clear */
+  0,                            /* tp_richcompare */
+  0,                            /* tp_weaklistoffset */
+  0,                            /* tp_iter */
+  0,                            /* tp_iternext */
+  _PyGstClockID_methods,        /* tp_methods */
+  0,                            /* tp_members */
+  0,                            /* tp_getset */
+  0,                            /* tp_base */
+  0,                            /* tp_dict */
+  0,                            /* tp_descr_get */
+  0,                            /* tp_descr_set */
+  0,                            /* tp_dictoffset */
+  0,                            /* tp_init */
+  0,                            /* tp_alloc */
+  (newfunc) pygst_clock_id__new__  /* tp_new */
+};
diff -Naur -x .svn gst-python-orig/gst/pygstexception.c gst-python/gst/pygstexception.c
--- gst-python-orig/gst/pygstexception.c	2006-12-14 19:48:06.000000000 +0100
+++ gst-python/gst/pygstexception.c	2006-12-14 19:47:03.000000000 +0100
@@ -29,7 +29,8 @@
 PyObject *PyGstExc_QueryError = NULL;
 PyObject *PyGstExc_RemoveError = NULL;
 PyObject *PyGstExc_PluginNotFoundError = NULL;
-
+PyObject *PyGstExc_ClockError = NULL;
+PyObject *PyGstExc_ClockIDUnscheduled = NULL;
 
 static PyObject *
 call_exception_init(PyObject *args)
@@ -225,6 +226,28 @@
 
     Py_DECREF(PyGstExc_PluginNotFoundError);
     
+    /* register gst.ClockError */
+    PyGstExc_ClockError = PyErr_NewException("gst.ClockError", NULL, NULL);
+    if (PyGstExc_ClockError == NULL)
+        goto exception;
+
+    if (PyDict_SetItemString(d, "ClockError", PyGstExc_ClockError) < 0)
+        goto exception;
+
+    Py_DECREF (PyGstExc_ClockError);
+
+    /* register gst.ClockIDUnscheduled */
+    PyGstExc_ClockIDUnscheduled = PyErr_NewException("gst.ClockIDUnscheduled",
+	         PyGstExc_ClockError, NULL);
+    if (PyGstExc_ClockIDUnscheduled == NULL)
+        goto exception;
+
+    if (PyDict_SetItemString(d, "ClockIDUnscheduled",
+	    PyGstExc_ClockIDUnscheduled) < 0)
+        goto exception;    
+
+    Py_DECREF(PyGstExc_ClockIDUnscheduled);
+
     return;
     
 exception:
@@ -234,6 +257,8 @@
     Py_XDECREF(PyGstExc_RemoveError);
     Py_XDECREF(PyGstExc_QueryError);
     Py_XDECREF(PyGstExc_PluginNotFoundError);
+    Py_XDECREF(PyGstExc_ClockError);
+    Py_XDECREF(PyGstExc_ClockIDUnscheduled);
 
     return;
 }
diff -Naur -x .svn gst-python-orig/gst/pygstexception.h gst-python/gst/pygstexception.h
--- gst-python-orig/gst/pygstexception.h	2006-12-14 19:48:06.000000000 +0100
+++ gst-python/gst/pygstexception.h	2006-12-14 19:47:03.000000000 +0100
@@ -29,6 +29,8 @@
 extern PyObject *PyGstExc_RemoveError;
 extern PyObject *PyGstExc_QueryError;
 extern PyObject *PyGstExc_PluginNotFoundError;
+extern PyObject *PyGstExc_ClockError;
+extern PyObject *PyGstExc_ClockIDUnscheduled;
 
 void pygst_exceptions_register_classes(PyObject *d);
 
diff -Naur -x .svn gst-python-orig/testsuite/Makefile.am gst-python/testsuite/Makefile.am
--- gst-python-orig/testsuite/Makefile.am	2006-12-14 19:48:05.000000000 +0100
+++ gst-python/testsuite/Makefile.am	2006-12-14 19:47:02.000000000 +0100
@@ -22,6 +22,7 @@
 	test_bin.py \
 	test_buffer.py \
         test_caps.py \
+	test_clock.py \
 	test_element.py \
 	test_event.py \
 	test_ghostpad.py \
diff -Naur -x .svn gst-python-orig/testsuite/test_clock.py gst-python/testsuite/test_clock.py
--- gst-python-orig/testsuite/test_clock.py	1970-01-01 01:00:00.000000000 +0100
+++ gst-python/testsuite/test_clock.py	2006-12-14 19:47:02.000000000 +0100
@@ -0,0 +1,290 @@
+# -*- Mode: Python -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# test_clock.py: Testsuite for SystemClock and ClockID objects
+#
+# Copyright (C) 2006 Rene Stadler <mail@renestadler.de>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+#
+
+from common import gst, unittest, TestCase
+
+import sys, gc
+from time import sleep
+from threading import Thread, Event
+
+import gobject
+gobject.threads_init ()
+
+def seconds (nsecs):
+    return float (nsecs) / gst.SECOND
+
+class SystemClockTestBase (TestCase):
+
+    # Enable GC verification for ClockID objects:
+    _types = list (TestCase._types) + [gst.ClockID]
+
+    def setUp (self):
+
+        # This ensures that the global system clock instance has already been
+        # created.  If we don't do this, the first test that is run will fail
+        # the GC verification because it would appear to leak a reference to the
+        # clock.
+        gst.system_clock_obtain ()
+
+        TestCase.setUp (self)
+        self.clock = gst.system_clock_obtain ()
+        self.base_time = self.clock.get_time ()
+
+    def tearDown (self):
+
+        del self.clock
+        del self.base_time
+        TestCase.tearDown (self)
+
+class ClockIDTest (SystemClockTestBase):
+    
+    def testGetTime (self):
+
+        clock_id1 = self.clock.new_single_shot_id (self.base_time + gst.SECOND)
+        self.assertEquals (clock_id1.get_time (), self.base_time + gst.SECOND)
+
+        clock_id2 = self.clock.new_periodic_id (self.base_time + gst.SECOND * 2,
+                                                gst.MSECOND * 100)
+        self.assertEquals (clock_id2.get_time (),
+                           self.base_time + gst.SECOND * 2)
+
+    def testClockTimeNone (self):
+
+        clock_id1 = self.clock.new_single_shot_id (gst.CLOCK_TIME_NONE)
+        self.assertEquals (clock_id1.get_time (), gst.CLOCK_TIME_NONE)
+
+        self.assertRaises (ValueError, clock_id1.wait)
+
+        got_callback = Event ()
+        cb_args = []
+        def async_callback (*a):
+            cb_args.extend (list (a))
+            got_callback.set ()
+        def wait_async ():
+            clock_id1.wait_async (async_callback)
+            
+        # We fully the support the confusion that the underlying API provides:
+        # The callback is called immediately and wait_async returns BADTIME,
+        # which we translate to an exception accordingly.
+
+        self.assertRaises (ValueError, wait_async)
+        self.failUnless (got_callback.isSet ())
+
+        cb_clock, cb_time, cb_clock_id = cb_args
+        self.assertEquals (cb_clock, self.clock)
+        self.assertEquals (cb_time, gst.CLOCK_TIME_NONE)
+        self.assertEquals (cb_clock_id, clock_id1)
+
+        # It is not supported for periodic clock ids:
+
+        def new_periodic_id1 ():
+            self.clock.new_periodic_id (gst.CLOCK_TIME_NONE, gst.SECOND)
+        self.assertRaises (ValueError, new_periodic_id1)
+
+        def new_periodic_id2 ():
+            self.clock.new_periodic_id (self.base_time, gst.CLOCK_TIME_NONE)
+        self.assertRaises (ValueError, new_periodic_id2)
+
+        # Not CLOCK_TIME_NONE related, but while we're at it:
+        def new_periodic_id3 ():
+            self.clock.new_periodic_id (self.base_time, 0)
+        self.assertRaises (ValueError, new_periodic_id3)
+
+    def testWait1 (self):
+
+        delta = gst.MSECOND * 500
+        lapse = gst.MSECOND * 100
+        clock_id = self.clock.new_single_shot_id (self.base_time + delta)
+        
+        sleep (seconds (lapse))
+        jitter = clock_id.wait ()
+        # We called in time, jitter must be negative:
+        self.failUnless (jitter < 0)
+
+    def testWait2 (self):
+
+        delta = gst.MSECOND * 100
+        lapse = gst.MSECOND * 200
+        clock_id = self.clock.new_single_shot_id (self.base_time + delta)
+        
+        sleep (seconds (lapse))
+        jitter = clock_id.wait ()
+        # We called in late, jitter must be positive:
+        self.failUnless (jitter > 0)
+
+    def testWaitAsync (self):
+
+        got_callback = Event ()
+        callback_args = []
+        def async_callback (cb_clock, cb_time, cb_clock_id, *cb_args):
+            callback_args.extend ([cb_clock, cb_time, cb_clock_id, cb_args])
+            got_callback.set ()
+        delta = gst.MSECOND * 200
+
+        clock_id = self.clock.new_single_shot_id (self.base_time + delta)
+        clock_id.wait_async (async_callback, "", None, 0, False)
+        got_callback.wait (seconds (gst.SECOND))
+        self.failUnless (got_callback.isSet ())
+
+        cb_clock, cb_time, cb_clock_id, cb_args = callback_args
+        self.assertEquals (cb_clock, self.clock)
+        self.assertEquals (cb_time, self.base_time + delta)
+        self.assertEquals (cb_clock_id, clock_id)
+        self.assertEquals (cb_args, ("", None, 0, False,))
+
+    def testInvalidWait (self):
+
+        # Unscheduling an id makes it useless, trying to .wait () should raise a
+        # proper exception:
+
+        clock_id = self.clock.new_single_shot_id (self.base_time + gst.SECOND)
+        
+        clock_id.unschedule ()        
+        self.assertRaises (gst.ClockIDUnscheduled, clock_id.wait)
+
+    def testInvalidWaitAsync (self):
+
+        # Same for .wait_async ():
+
+        got_callback = Event ()
+        def async_callback (*a):
+            got_callback.set ()
+        delta = gst.MSECOND * 200
+
+        clock_id = self.clock.new_single_shot_id (self.base_time + delta)
+        def wait_async ():
+            clock_id.wait_async (async_callback)
+
+        clock_id.unschedule ()
+        self.assertRaises (gst.ClockIDUnscheduled, wait_async)
+
+        # Ensure that the callback does not get called:
+        got_callback.wait (seconds (2 * delta))
+        self.failUnless (not got_callback.isSet ())
+
+    def testAbortWait (self):
+
+        # The real use case for .unschedule is to be able to abort a blocking
+        # .wait as well as an outstanding .wait_async notification:
+
+        got_abort = Event ()
+        delta = gst.SECOND * 10
+
+        clock_id = self.clock.new_single_shot_id (self.base_time + delta)
+        class TestThread (Thread):
+            def run (self_thread):
+                try:
+                    clock_id.wait ()
+                except gst.ClockIDUnscheduled:
+                    got_abort.set ()
+        thread = TestThread ()
+        thread.start ()
+        
+        # Give the thread some time to spin up and get to the .wait:
+        sleep (seconds (gst.MSECOND * 200))
+        self.failUnless (not got_abort.isSet ())
+        self.failUnless (thread.isAlive ())
+
+        clock_id.unschedule ()
+        
+        got_abort.wait (seconds (gst.SECOND))
+        self.failUnless (got_abort.isSet ())
+
+    def testAbortWaitAsync (self):
+
+        # Same for .wait_async:
+
+        got_callback = Event ()
+        delta = gst.MSECOND * 500
+        
+        clock_id = self.clock.new_single_shot_id (self.base_time + delta)
+        def async_callback (*a):
+            got_callback.set ()
+
+        clock_id.wait_async (async_callback)
+        clock_id.unschedule ()
+        # Ensure that the callback goes not get called:
+        got_callback.wait (seconds (2 * delta))
+        self.failUnless (not got_callback.isSet ())
+
+    def testComparison (self):
+
+        # ClockID objects implement __cmp__ based on their clock time, which
+        # allows for sorting them meaningfully.
+
+        time1 = self.base_time + gst.SECOND * 2
+        time2 = self.base_time + gst.SECOND * 40
+        time3 = self.base_time + gst.SECOND * 60
+        clock_id1 = self.clock.new_single_shot_id (time1)
+        clock_id2 = self.clock.new_periodic_id (time2, gst.SECOND)
+        clock_id3 = self.clock.new_single_shot_id (time3)
+
+        self.assertEquals (tuple (sorted ((clock_id3, clock_id1, clock_id2,))),
+                           (clock_id1, clock_id2, clock_id3,))
+
+    def testInvalidConstructor (self):
+
+        # Creation of ClockID objects by calling the class is forbidden, as that
+        # would create a useless object.  The __new__ implementation raises an
+        # exception instead, which points the puzzled user to the factory
+        # functions.
+
+        self.assertRaises (NotImplementedError, gst.ClockID)
+
+    def testRefCycle (self):
+
+        # Because ClockID objects can store arbitrary python objects for the
+        # callback passed to wait_async, they are container objects and must
+        # play well with the GC to let it detect and break reference cycles.
+
+        data = ["some arbitrary argument (a list with a string)"]
+        def callback (clock, time, clock_id_, *args):
+            pass
+        delta = gst.MSECOND * 200
+
+        clock_id = self.clock.new_single_shot_id (self.base_time + delta)
+
+        self.assertEquals (sys.getrefcount (clock_id), 2)
+        self.assertEquals (sys.getrefcount (callback), 2)
+        self.assertEquals (sys.getrefcount (data), 2)
+        try:
+            # Storing the clock id in the callback user data creates a
+            # reference cycle:
+            clock_id.wait_async (callback, clock_id, data)
+            self.assertEquals (sys.getrefcount (clock_id), 3)
+            self.assertEquals (sys.getrefcount (callback), 3)
+            self.assertEquals (sys.getrefcount (data), 3)
+            del clock_id
+            gc.collect ()
+            gc.collect ()
+            self.assertEquals (sys.getrefcount (callback), 2)
+            self.assertEquals (sys.getrefcount (data), 2)
+        finally:
+            sleep (seconds (2 * delta))
+
+        # If we succeeded above, the garbage collector properly tracks our
+        # ClockID object.  In TestCase.tearDown, the test framework asks the
+        # garbage collector if there is a leaked reference and makes the test
+        # fail.
+
+if __name__ == "__main__":
+    unittest.main()
