1# PyGST Tutorial/States, and the Bus
2
3The material in this section may not seem all that exciting, but serves
4as the foundation for more interesting topics. Hopefully now that I've
5captured your attention with a few sexy examples, we can get through
6this rather dreary subject. In this tutorial, we explore *element
7state*and the *element life-cycle*. We will also examine the *message
8bus* in further detail, and how this relates to tracking state changes.
9Finally, will use this information to polish up our demonstration
10framework.
11
12# States
13
14There are four element states: `gst.STATE_NULL`, `gst.STATE_READY`,
15`gst.STATE_PAUSED`, and `gst.STATE_PLAYING`. The distinction between
16`NULL` and `READY` states deserve some explanation.
17
18`gst.STATE_NULL`
19
20:   when state is null resources needed for a gst.Element have not been
21    loaded, these can be libraries, devices etc. This is the first state
22    of any gst.Element.
23
24`gst.STATE_READY`
25
26:   when state is ready resources have been allocated, and the
27    gst.Element is ready to be used.
28
29`gst.STATE_PAUSED`
30
31:
32
33`gst.STATE_PLAYING`
34
35:
36
37## The element life-cycle
38
39![](element_lifecycle.png "element_lifecycle.png")
40
41The above illustration summarizes the element life-cycle.
42
43## Dataflow and Blocking
44
45So far, we've only talked about *element state*. However, *pads* also
46have state. Pads can be either *blocked* or *unblocked*. In order for a
47pipeline to transition into the playing state, all the pads of all the
48elements in the pipeline must be in the *unblocked* state.
49
50## Adding a Pause Button
51
52[source for this example](enhanced_demo.py.md)
53
54Let's use what we've learned to modify demo.py to add a *pause* button.
55
56First, add the pause button to the UI layout:
57
58        def createWindow(self):
59
60        ...
61
62            # declare buttons and their associated handlers
63            controls = (
64                ("play_button", gtk.ToolButton(gtk.STOCK_MEDIA_PLAY), self.onPlay),
65                ("pause_button", gtk.ToolButton(gtk.STOCK_MEDIA_PAUSE), self.onPause),
66                ("stop_button", gtk.ToolButton(gtk.STOCK_MEDIA_STOP), self.onStop),
67                ("quit_button", gtk.ToolButton(gtk.STOCK_QUIT), gtk.main_quit)
68            )
69
70        ...
71
72Next, define the `onPause()` handler. This handler simply sets the
73pipeline state to the `gst.PAUSED` state.
74
75        def onPause(self, unused_button):
76            self.pipeline.set_state(gst.STATE_PAUSED)
77
78[source for this example](enhanced_demo.py.md)
79
80## Running this Example
81
82This example serves as a drop-in replacement for our original `demo.py`.
83You can run this file stand-alone, but it will be more interesting to
84save it as `demo.py` and re-run the previous examples.
85
86# The Bus
87
88We have seen the Bus before. Remember this code from the first example?
89
90            # this code receives the messages from the pipeline. if we
91            # need to set X11 id, then we call set_xid
92            def bus_handler(unused_bus, message):
93                if message.type == gst.MESSAGE_ELEMENT:
94                    if message.structure.get_name() == 'prepare-xwindow-id':
95                        set_xid(w)
96                return gst.BUS_PASS
97
98            # create our pipeline, and connect our bus_handler
99            self.pipeline = gst.Pipeline()
100            bus = self.pipeline.get_bus()
101            bus.set_sync_handler(bus_handler)
102
103The bus is one mechanism that GStreamer pipelines use to communicate
104with the application. Elements can post messages onto the Bus from any
105thread, and the messages will be received by the application's main
106thread. Handling bus messages is central to many aspects of GStreamer
107application development, including tracking state changes.
108
109## Connecting to the Bus
110
111There are three ways to connect to the bus: with a *watch*, a *signal
112watch*, and a *sync handler*.
113
114-   A watch is a simple call back, which you register by calling
115    `Bus.add_watch()`.
116-   If you need more flexibility, add a signal watch by calling
117    `Bus.add_signal_watch()`. This causes the bus to emit the `message`
118    signal, which you can connect to. The `message` signal is not
119    emitted unless you call `add_signal_watch()`
120-   If you want to receive bus messages in the same thread from which
121    they are posted, call `Bus.set_sync_handler`. You should probably
122    avoid this method unless you understand how to to write
123    multi-threaded code.
124
125## Messages
126
127There are many message types, including the `gst.MESSAGE_ELEMENT` shown
128above. Here's a list of some common ones, and what they mean:
129
130  Message Type                  Meaning
131  ----------------------------- -----------------------------------------------
132  `gst.MESSAGE_ELEMENT`         message type for element-specific information
133  `gst.MESSAGE_EOS`             the end of the pipeline has been reached
134  `gst.MESSAGE_ERROR`           a pipeline error has occurred
135  `gst.MESSAGE_SEGMENT_DONE`    the pipeline has completed a *segment seek*
136  `gst.MESSAGE_STATE_CHANGED`   an element's state has changed
137  `gst.MESSAGE_TAG`             meta-data has been decoded from the stream
138
139We'll talk more about that last message, `gst.MESSAGE_SEGMENT_DONE`, in
140the next article.
141
142The `gst.Message` object is generic, and information is extracted using
143the `parse_*()` set of functions depending on the message type. For
144example, to parse the `gst.MESSAGE_STATE_CHANGED` message, use the
145`parse_state_changed()` method. You may also need to access the
146message's `structure` attribute directly (as we must to do set the
147`ximagesink` element's `xwindow_id`).
148
149## Providing State-Change Feedback
150
151Let's use what we've learned to provide feedback to the user about state
152changes. We want the sensitivity of the playback controls to reflect the
153current state of the pipeline.
154
155[source for this example](enhanced_demo.py.md)
156
157The following table summarizes the sensitivity that Play, Pause, and
158Stop buttons should have in each of the GStreamer states.
159
160  State                 Play Button   Pause Button   Stop Button
161  --------------------- ------------- -------------- -------------
162  `gst.STATE_NULL`      Sensitive     Insensitive    Insensitive
163  `gst.STATE_READY`     Sensitive     Insensitive    Insensitive
164  `gst.STATE_PAUSED`    Sensitive     Insensitive    Sensitive
165  `gst.STATE_PLAYING`   Insensitive   Sensitive      Sensitive
166
167This is easily translated into an else-if chain. What we will do is
168update the sensitivity of all the buttons according to this chart when
169we get a state-changed message.
170
171        def updateButtons(self, state):
172            if state == gst.STATE_NULL:
173                self.play_button.set_sensitive(True)
174                self.pause_button.set_sensitive(False)
175                self.stop_button.set_sensitive(False)
176            elif state == gst.STATE_READY:
177                self.play_button.set_sensitive(True)
178                self.pause_button.set_sensitive(False)
179                self.stop_button.set_sensitive(False)
180            elif state == gst.STATE_PAUSED:
181                self.play_button.set_sensitive(True)
182                self.pause_button.set_sensitive(False)
183                self.stop_button.set_sensitive(True)
184            elif state == gst.STATE_PLAYING:
185                self.play_button.set_sensitive(False)
186                self.pause_button.set_sensitive(True)
187                self.stop_button.set_sensitive(True)
188
189Now, we just need to look for the `gst.MESSAGE_STATE_CHANGED` message on
190the bus, and call `updateButtons` with the new state. First let's define
191our message handler:
192
193         def messageCb(self, bus, message):
194            if message.type == gst.MESSAGE_STATE_CHANGED:
195                old, new, pending = message.parse_state_changed()
196                self.updateButtons(new)
197            return gst.BUS_PASS
198
199We want to receive these element messages in the main thread, since we
200are using the information to update the UI. Therefore, we modify
201`createPipeline()` to connect to our message handler as a bus watch.
202
203One last detail: update the state of the buttons with the null state in
204`createWindow()`
205
206[source for this example](enhanced_demo.py.md)
207
208## Running this Example
209
210This example serves as a drop-in replacement for our original `demo.py`.
211You can run this file stand-alone, but it will be more interesting to
212save it as `demo.py` and re-run the previous examples.
213
214## Handling Pipeline Errors
215
216Our demonstration framework has not done any error handling to speak of.
217Let's fix that.
218