pitivi r1292 - in trunk/pitivi: . ui



Author: edwardrv
Date: Sat Sep 27 17:40:27 2008
New Revision: 1292
URL: http://svn.gnome.org/viewvc/pitivi?rev=1292&view=rev

Log:
UI : Allow selection of input devices for capture

Modified:
   trunk/pitivi/bin.py
   trunk/pitivi/ui/cam_capture.glade
   trunk/pitivi/ui/mainwindow.py
   trunk/pitivi/ui/webcam_managerdialog.py

Modified: trunk/pitivi/bin.py
==============================================================================
--- trunk/pitivi/bin.py	(original)
+++ trunk/pitivi/bin.py	Sat Sep 27 17:40:27 2008
@@ -565,32 +565,39 @@
     SmartBin derivative for capturing streams.
     """
 
-    # FIXME : THESE ARE HARDCODED PLUGINS !!!
-    # We should use a system allowing you to see all available usable
-    # input HW device + plugin and use that instead
-
-    def __init__(self):
+    def __init__(self, audiodevice=None, videodevice=None):
         gst.log("Creating new smartcapturebin")
-        self.videosrc = gst.element_factory_make("v4l2src", "webcam-vsrc")
-        self.audiosrc = gst.element_factory_make("alsasrc", "webcam-asrc")
-
-        SmartBin.__init__(self, "smartcapturebin", has_video=True, has_audio=True,
+        self.__audiodev = audiodevice
+        self.__videodev = videodevice
+        self.videosrc = None
+        self.audiosrc = None
+
+        SmartBin.__init__(self, "smartcapturebin",
+                          has_video=bool(videodevice),
+                          has_audio=bool(audiodevice),
                           width=640, height=490)
 
 
     def _addSource(self):
-        self.q1 = gst.element_factory_make("queue", "webcam-firstvqueue")
-        self.q1.props.max_size_time = 10 * gst.SECOND
-        self.q2 = gst.element_factory_make("queue", "webcam-firstaqueue")
-        self.q2.props.max_size_time = 30 * gst.SECOND
-        self.q2.props.max_size_buffers = 0
-        self.q2.props.max_size_bytes = 0
-        self.add(self.videosrc, self.audiosrc, self.q1, self.q2)
+        if self.__audiodev:
+            self.audiosrc = self.__audiodev.makeAudioBin()
+            self.q2 = gst.element_factory_make("queue", "webcam-firstaqueue")
+            self.q2.props.max_size_time = 30 * gst.SECOND
+            self.q2.props.max_size_buffers = 0
+            self.q2.props.max_size_bytes = 0
+            self.add(self.audiosrc, self.q2)
+        if self.__videodev:
+            self.videosrc = self.__videodev.makeVideoBin()
+            self.q1 = gst.element_factory_make("queue", "webcam-firstvqueue")
+            self.q1.props.max_size_time = 10 * gst.SECOND
+            self.add(self.videosrc, self.q1)
 
     def _connectSource(self):
         self.debug("connecting sources")
-        gst.element_link_many(self.videosrc, self.q1, self.vtee)
-        gst.element_link_many(self.audiosrc, self.q2, self.atee)
+        if self.videosrc:
+            gst.element_link_many(self.videosrc, self.q1, self.vtee)
+        if self.audiosrc:
+            gst.element_link_many(self.audiosrc, self.q2, self.atee)
         self.debug("finished connecting sources")
 
     def _asyncReset(self, uri, setting):
@@ -608,10 +615,12 @@
         #
         # This is fixed in gst-plugins-base cvs 0.10.20.1
         self.debug("Setting sources to NULL again to reset their timestamps !")
-        self.videosrc.set_state(gst.STATE_NULL)
-        self.videosrc.set_state(gst.STATE_READY)
-        self.audiosrc.set_state(gst.STATE_NULL)
-        self.audiosrc.set_state(gst.STATE_READY)
+        if self.videosrc:
+            self.videosrc.set_state(gst.STATE_NULL)
+            self.videosrc.set_state(gst.STATE_READY)
+        if self.audiosrc:
+            self.audiosrc.set_state(gst.STATE_NULL)
+            self.audiosrc.set_state(gst.STATE_READY)
 
         SmartBin.record(self, uri, settings)
 

Modified: trunk/pitivi/ui/cam_capture.glade
==============================================================================
--- trunk/pitivi/ui/cam_capture.glade	(original)
+++ trunk/pitivi/ui/cam_capture.glade	Sat Sep 27 17:40:27 2008
@@ -1,221 +1,134 @@
-<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
-<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd";>
-
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Sat Sep 27 15:43:58 2008 -->
 <glade-interface>
-
-<widget class="GtkDialog" id="cam_capture">
-  <property name="width_request">530</property>
-  <property name="height_request">500</property>
-  <property name="visible">True</property>
-  <property name="title" translatable="yes">Record from Webcam</property>
-  <property name="type">GTK_WINDOW_TOPLEVEL</property>
-  <property name="window_position">GTK_WIN_POS_NONE</property>
-  <property name="modal">False</property>
-  <property name="resizable">True</property>
-  <property name="destroy_with_parent">False</property>
-  <property name="decorated">True</property>
-  <property name="skip_taskbar_hint">False</property>
-  <property name="skip_pager_hint">False</property>
-  <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
-  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
-  <property name="focus_on_map">True</property>
-  <property name="urgency_hint">False</property>
-  <property name="has_separator">True</property>
-
-  <child internal-child="vbox">
-    <widget class="GtkVBox" id="dialog-vbox1">
-      <property name="visible">True</property>
-      <property name="homogeneous">False</property>
-      <property name="spacing">0</property>
-
-      <child internal-child="action_area">
-	<widget class="GtkHButtonBox" id="dialog-action_area1">
-	  <property name="visible">True</property>
-	  <property name="layout_style">GTK_BUTTONBOX_END</property>
-
-	  <child>
-	    <widget class="GtkButton" id="record_btn">
-	      <property name="visible">True</property>
-	      <property name="can_default">True</property>
-	      <property name="can_focus">True</property>
-	      <property name="relief">GTK_RELIEF_NORMAL</property>
-	      <property name="focus_on_click">True</property>
-	      <property name="response_id">0</property>
-
-	      <child>
-		<widget class="GtkAlignment" id="btn_record">
-		  <property name="visible">True</property>
-		  <property name="xalign">0.5</property>
-		  <property name="yalign">0.5</property>
-		  <property name="xscale">0</property>
-		  <property name="yscale">0</property>
-		  <property name="top_padding">0</property>
-		  <property name="bottom_padding">0</property>
-		  <property name="left_padding">0</property>
-		  <property name="right_padding">0</property>
-
-		  <child>
-		    <widget class="GtkHBox" id="hbox2">
-		      <property name="visible">True</property>
-		      <property name="homogeneous">False</property>
-		      <property name="spacing">2</property>
-
-		      <child>
-			<widget class="GtkImage" id="image2">
-			  <property name="visible">True</property>
-			  <property name="stock">gtk-media-record</property>
-			  <property name="icon_size">4</property>
-			  <property name="xalign">0.5</property>
-			  <property name="yalign">0.5</property>
-			  <property name="xpad">0</property>
-			  <property name="ypad">0</property>
-			</widget>
-			<packing>
-			  <property name="padding">0</property>
-			  <property name="expand">False</property>
-			  <property name="fill">False</property>
-			</packing>
-		      </child>
-
-		      <child>
-			<widget class="GtkLabel" id="lbl">
-			  <property name="visible">True</property>
-			  <property name="label" translatable="yes">Start Recording</property>
-			  <property name="use_underline">True</property>
-			  <property name="use_markup">False</property>
-			  <property name="justify">GTK_JUSTIFY_LEFT</property>
-			  <property name="wrap">False</property>
-			  <property name="selectable">False</property>
-			  <property name="xalign">0.5</property>
-			  <property name="yalign">0.5</property>
-			  <property name="xpad">0</property>
-			  <property name="ypad">0</property>
-			  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
-			  <property name="width_chars">-1</property>
-			  <property name="single_line_mode">False</property>
-			  <property name="angle">0</property>
-			</widget>
-			<packing>
-			  <property name="padding">0</property>
-			  <property name="expand">False</property>
-			  <property name="fill">False</property>
-			</packing>
-		      </child>
-		    </widget>
-		  </child>
-		</widget>
-	      </child>
-	    </widget>
-	  </child>
-
-	  <child>
-	    <widget class="GtkButton" id="close_btn">
-	      <property name="visible">True</property>
-	      <property name="can_default">True</property>
-	      <property name="can_focus">True</property>
-	      <property name="relief">GTK_RELIEF_NORMAL</property>
-	      <property name="focus_on_click">True</property>
-	      <property name="response_id">0</property>
-
-	      <child>
-		<widget class="GtkAlignment" id="alignment3">
-		  <property name="visible">True</property>
-		  <property name="xalign">0.5</property>
-		  <property name="yalign">0.5</property>
-		  <property name="xscale">0</property>
-		  <property name="yscale">0</property>
-		  <property name="top_padding">0</property>
-		  <property name="bottom_padding">0</property>
-		  <property name="left_padding">0</property>
-		  <property name="right_padding">0</property>
-
-		  <child>
-		    <widget class="GtkHBox" id="hbox3">
-		      <property name="visible">True</property>
-		      <property name="homogeneous">False</property>
-		      <property name="spacing">2</property>
-
-		      <child>
-			<widget class="GtkImage" id="image3">
-			  <property name="visible">True</property>
-			  <property name="stock">gtk-close</property>
-			  <property name="icon_size">4</property>
-			  <property name="xalign">0.5</property>
-			  <property name="yalign">0.5</property>
-			  <property name="xpad">0</property>
-			  <property name="ypad">0</property>
-			</widget>
-			<packing>
-			  <property name="padding">0</property>
-			  <property name="expand">False</property>
-			  <property name="fill">False</property>
-			</packing>
-		      </child>
-
-		      <child>
-			<widget class="GtkLabel" id="label3">
-			  <property name="visible">True</property>
-			  <property name="label" translatable="yes">Close</property>
-			  <property name="use_underline">True</property>
-			  <property name="use_markup">False</property>
-			  <property name="justify">GTK_JUSTIFY_LEFT</property>
-			  <property name="wrap">False</property>
-			  <property name="selectable">False</property>
-			  <property name="xalign">0.5</property>
-			  <property name="yalign">0.5</property>
-			  <property name="xpad">0</property>
-			  <property name="ypad">0</property>
-			  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
-			  <property name="width_chars">-1</property>
-			  <property name="single_line_mode">False</property>
-			  <property name="angle">0</property>
-			</widget>
-			<packing>
-			  <property name="padding">0</property>
-			  <property name="expand">False</property>
-			  <property name="fill">False</property>
-			</packing>
-		      </child>
-		    </widget>
-		  </child>
-		</widget>
-	      </child>
-	    </widget>
-	  </child>
-	</widget>
-	<packing>
-	  <property name="padding">0</property>
-	  <property name="expand">False</property>
-	  <property name="fill">True</property>
-	  <property name="pack_type">GTK_PACK_END</property>
-	</packing>
-      </child>
-
-      <child>
-	<widget class="GtkDrawingArea" id="draw_window">
-	  <property name="visible">True</property>
-	</widget>
-	<packing>
-	  <property name="padding">0</property>
-	  <property name="expand">True</property>
-	  <property name="fill">True</property>
-	</packing>
-      </child>
-
-      <child>
-	<widget class="GtkStatusbar" id="statusbar1">
-	  <property name="visible">True</property>
-	  <property name="has_resize_grip">True</property>
-	</widget>
-	<packing>
-	  <property name="padding">0</property>
-	  <property name="expand">False</property>
-	  <property name="fill">False</property>
-	  <property name="pack_type">GTK_PACK_END</property>
-	</packing>
-      </child>
-    </widget>
-  </child>
-</widget>
-
+  <widget class="GtkDialog" id="cam_capture">
+    <property name="border_width">5</property>
+    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <child>
+              <widget class="GtkDrawingArea" id="draw_window">
+                <property name="width_request">320</property>
+                <property name="height_request">240</property>
+                <property name="visible">True</property>
+              </widget>
+              <packing>
+                <property name="padding">5</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkTable" id="table1">
+                <property name="visible">True</property>
+                <property name="n_rows">2</property>
+                <property name="n_columns">2</property>
+                <property name="column_spacing">2</property>
+                <property name="row_spacing">2</property>
+                <child>
+                  <widget class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="xpad">5</property>
+                    <property name="label" translatable="yes">Audio Capture Device :</property>
+                  </widget>
+                  <packing>
+                    <property name="x_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="xpad">5</property>
+                    <property name="label" translatable="yes">Video Capture Device :</property>
+                  </widget>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkComboBox" id="audiodev_combo">
+                    <property name="visible">True</property>
+                    <property name="active">0</property>
+                    <property name="items" translatable="yes">No device available</property>
+                    <signal name="changed" handler="_adevComboChangedCb"/>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkComboBox" id="videodev_combo">
+                    <property name="visible">True</property>
+                    <property name="active">0</property>
+                    <property name="items" translatable="yes">No device available	</property>
+                    <signal name="changed" handler="_vdevComboChangedCb"/>
+                  </widget>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="padding">5</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <child>
+              <widget class="GtkButton" id="record_btn">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-media-record</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="close_btn">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="label" translatable="yes">gtk-close</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">0</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
 </glade-interface>

Modified: trunk/pitivi/ui/mainwindow.py
==============================================================================
--- trunk/pitivi/ui/mainwindow.py	(original)
+++ trunk/pitivi/ui/mainwindow.py	Sat Sep 27 17:40:27 2008
@@ -452,8 +452,8 @@
 
     # Import from Webcam callback
     def _ImportWebcam(self,unused_action):
-        WebcamManagerDialog()
-    
+        w = WebcamManagerDialog(instance.PiTiVi)
+        w.show()
 
     # Capture network stream callback
     def _ImportNetstream(self,unused_action):

Modified: trunk/pitivi/ui/webcam_managerdialog.py
==============================================================================
--- trunk/pitivi/ui/webcam_managerdialog.py	(original)
+++ trunk/pitivi/ui/webcam_managerdialog.py	Sat Sep 27 17:40:27 2008
@@ -42,6 +42,8 @@
 
         # Create gtk widget using glade model
         self.draw_window = self.widgets["draw_window"]
+        self.draw_window.unset_flags(gtk.DOUBLE_BUFFERED)
+        self.draw_window.unset_flags(gtk.SENSITIVE)
         self.record_btn = self.widgets["record_btn"]
         self.close_btn = self.widgets["close_btn"]
 
@@ -55,14 +57,26 @@
 
         self.sourcefactories = SourceFactoriesWidget()
 
-        gst.debug("SmartCaptureBin player created")
-        self.player = SmartCaptureBin()
-        self.setSinks()
+        self._audiodev = None
+        self._videodev = None
+
+        self._vdevcombo = self.widgets["videodev_combo"]
+        self._vdevcombo.set_active(0)
+        self._vdevcombo.set_model(gtk.ListStore(str, object))
+        self._vdevcombo.set_attributes(self._vdevcombo.child.get_cell_renderers()[0],
+                                       text=0)
+        self._adevcombo = self.widgets["audiodev_combo"]
+        self._adevcombo.set_active(0)
+        self._adevcombo.set_model(gtk.ListStore(str, object))
+        self._adevcombo.set_attributes(self._adevcombo.child.get_cell_renderers()[0],
+                                       text=0)
+        self.__updateVideoCombo()
+        self.__updateAudioCombo()
 
         self.filepath = None
 
-        # Not a good idea to do this at this point
-        self.player.set_state(gst.STATE_PLAYING)
+        self.sink = SinkBin()
+        CallbackThread(self.__setupPlayer).start()
 
     def show_all(self):
         self.window.show_all()
@@ -76,7 +90,8 @@
     def do_recording(self, w):
         if self.record_btn.get_label() == "Start Recording":
             gst.debug("recording started")
-            self.filepath = 'file://'+tempfile.mktemp()+'.ogg'
+            self.filepath = 'file://'+tempfile.mktemp(suffix=".ogg",
+                                                      prefix="pitivi-webcam-capture-")
             self.player.record(self.filepath, ExportSettings())
             self.record_btn.set_label("Stop Recording")
             self.player.set_state(gst.STATE_PLAYING)
@@ -86,14 +101,14 @@
         else:
             gst.debug("recording stopped")
             self.player.stopRecording()
+            # FIXME : use the generic way for adding a file
             self.sourcefactories.sourcelist.addFiles([self.filepath])
             self.player.set_state(gst.STATE_PLAYING)
             self.record_btn.set_label("Start Recording")
 
     # For Setting up audio,video sinks
     def setSinks(self):
-        sink = SinkBin()
-        sink.connectSink(self.player, True, True)
+        self.sink.connectSink(self.player, True, True)
         bus = self.player.get_bus()
         bus.add_signal_watch()
         bus.enable_sync_message_emission()
@@ -116,5 +131,131 @@
             try:
                 imagesink.set_xwindow_id(self.draw_window.window.xid)
             except:
-                print "OH OH"
+                gst.warning("Couldn't set the XID on our video sink !")
+
+    def __setupPlayer(self):
+        gst.debug("Creating initial SmartCaptureBin")
+        # figure out adev
+        probe = self.pitivi.deviceprobe
+        if len(probe.getAudioSourceDevices()):
+            adev = probe.getAudioSourceDevices()[0]
+        else:
+            adev = None
+        self.__changeSelectedAudio(adev)
+
+        if len(probe.getVideoSourceDevices()):
+            vdev = probe.getVideoSourceDevices()[0]
+        else:
+            vdev = None
+        self.__changeSelectedVideo(vdev)
+
+        probe.connect("device-added", self.__deviceAddedCb)
+        probe.connect("device-removed", self.__deviceRemovedCb)
+
+        if hasattr(self, "player"):
+            self.player.set_state(gst.STATE_NULL)
+        self.player = SmartCaptureBin(audiodevice=adev,
+                                      videodevice=vdev)
+        self.setSinks()
+        # FIXME : check for state change failures
+        self.player.set_state(gst.STATE_PLAYING)
+
+    def __resetPlayer(self):
+        ## call me in another thread !
+        gst.debug("Setting previous to NULL")
+        self.player.set_state(gst.STATE_NULL)
+        gst.debug("Creating new SmartCaptureBin(%r,%r)" % (self._audiodev, self._videodev))
+        self.player = SmartCaptureBin(audiodevice = self._audiodev,
+                                      videodevice = self._videodev)
+        gst.debug("Calling setSinks()")
+        self.setSinks()
+        gst.debug("Finally setting to PLAYING...")
+        res = self.player.set_state(gst.STATE_PLAYING)
+        gst.debug("... which returned %r" % res)
+
+    def __changeSelectedCombo(self, combo, device):
+        gst.debug("device %r" % device)
+        model = combo.get_model()
+        idx = 0
+        for name, dev in model:
+            if dev == device:
+                break
+            idx += 1
+        combo.set_active(idx)
+
+    def __changeSelectedAudio(self, device):
+        self._audiodev = device
+        self.__changeSelectedCombo(self._adevcombo, device)
+
+    def __changeSelectedVideo(self, device):
+        self._videodev = device
+        self.__changeSelectedCombo(self._vdevcombo, device)
+
+    def __deviceAddedCb(self, probe, device):
+        gst.debug("device %r appeared" % device)
+        self.__updateAudioCombo()
+        self.__updateVideoCombo()
+
+    def __deviceRemovedCb(self, probe, device):
+        gst.debug("device %r went away" % device)
+        if self._audiodev == device:
+            devs = self.pitivi.deviceprobe.getAudioSourceDevices()
+            if len(devs):
+                self.__changeSelectedAudio(devs[0])
+            else:
+                self._audiodev = None
+        elif self._videodev == device:
+            devs = self.pitivi.deviceprobe.getVideoSourceDevices()
+            if len(devs):
+                self.__changeSelectedVideo(devs[0])
+            else:
+                self._videodev = None
+        self.__updateAudioCombo()
+        self.__updateVideoCombo()
+
+    def __updateCombo(self, combo, devices):
+        model = combo.get_model()
+        if len(devices) == len(model):
+            # nothing changed
+            return
+        model.clear()
+        for dev in devices:
+            model.append([dev.displayname, dev])
+
+    def __updateAudioCombo(self):
+        self.__updateCombo(self._adevcombo,
+                           self.pitivi.deviceprobe.getAudioSourceDevices())
+        self.__changeSelectedAudio(self._audiodev)
+
+    def __updateVideoCombo(self):
+        self.__updateCombo(self._vdevcombo,
+                           self.pitivi.deviceprobe.getVideoSourceDevices())
+        self.__changeSelectedVideo(self._videodev)
+
+    def _adevComboChangedCb(self, widget):
+        # get the active device
+        row = widget.get_model()[widget.get_active()]
+        if len(row) < 2:
+            return
+        dev = row[1]
+        gst.debug("device %r" % dev)
+        if dev == self._audiodev:
+            return
+        self.__changeSelectedAudio(dev)
+        if not hasattr(self, "player"):
+            return
+        CallbackThread(self.__resetPlayer).start()
+
+    def _vdevComboChangedCb(self, widget):
+        row = widget.get_model()[widget.get_active()]
+        if len(row) < 2:
+            return
+        dev = row[1]
+        gst.debug("device %r" % dev)
+        if dev == self._videodev:
+            return
+        self.__changeSelectedVideo(dev)
+        if not hasattr(self, "player"):
+            return
+        CallbackThread(self.__resetPlayer).start()
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]