This URL has Read-Only access.

Statistics
| Branch: | Tag: | Revision:

root / py / scenic / streamer.py @ 07635ece

History | View | Annotate | Download (29.3 kB)

1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
# 
4
# Scenic
5
# Copyright (C) 2008 Société des arts technologiques (SAT)
6
# http://www.sat.qc.ca
7
# All rights reserved.
8
#
9
# This file is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 2 of the License, or
12
# (at your option) any later version.
13
#
14
# Scenic is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License
20
# along with Scenic. If not, see <http://www.gnu.org/licenses/>.
21

    
22
"""
23
Manages local streamer processes.
24
"""
25

    
26
from scenic import process
27
from scenic import sig
28
from scenic import dialogs
29
from scenic.internationalization import _
30

    
31
class StreamerManager(object):
32
    """
33
    Manages local streamer processes.
34
    """
35
    def __init__(self, app):
36
        self.app = app
37
        self.sender = None 
38
        self.receiver = None 
39
        self.extra_sender = None # extra sender/receiver used only if not synchronized
40
        self.extra_receiver = None
41
        
42
        self.midi_receiver = None
43
        self.midi_sender = None
44
        self.state = process.STATE_STOPPED
45
        self.state_changed_signal = sig.Signal()
46
        # for stats
47
        self.session_details = None # either None or a big dict
48
        self.rtcp_stats = None # either None or a big dict
49
        self.error_messages = None # either None or a big dict
50

    
51
    def _gather_config_to_stream(self, addr):
52
        """
53
        Gathers all settings in a big dict.
54
        
55
        Useful for feedback to the user.
56
        """
57
        contact_name = addr
58
        contact = self.app._get_contact_by_addr(addr)
59
        if contact is not None:
60
            contact_name = contact["name"]
61
        
62
        remote_config = self.app.remote_config # FIXME: should the remote config be passed as a param to this method?
63
        send_width, send_height = self.app.config.video_capture_size.split("x")
64
        receive_width, receive_height = remote_config["video"]["capture_size"].split("x")
65
        
66
        # MIDI
67
        midi_send_enabled = self.app.config.midi_send_enabled and remote_config["midi"]["recv_enabled"]
68
        midi_recv_enabled = self.app.config.midi_recv_enabled and remote_config["midi"]["send_enabled"]
69
        midi_input_device = self.app.config.midi_input_device
70
        midi_output_device = self.app.config.midi_output_device
71
        
72
        audio_jitterbuffer = self.app.config.audio_jitterbuffer
73
        if self.app.config.audio_video_synchronized and self.app.config.video_recv_enabled:
74
            audio_jitterbuffer = self.app.config.video_jitterbuffer
75
        
76
        print "remote_config:", remote_config
77
        
78
        self.session_details = {
79
            "peer": {
80
                "address": addr,
81
                "name": contact_name,
82
                },
83
            # ----------------- send ---------------
84
            "send": {
85
                "video": {
86
                    # Decided by both:
87
                    "enabled": self.app.config.video_send_enabled and remote_config["video"]["recv_enabled"],
88
                    
89
                    # Decided locally:
90
                    "source": self.app.config.video_source,
91
                    "device": self.app.config.video_device,
92
                    "width": int(send_width), # int
93
                    "height": int(send_height), # int
94
                    "aspect-ratio": self.app.config.video_aspect_ratio,
95
                    
96
                    # Decided by remote peer:
97
                    "port": remote_config["video"]["port"], 
98
                    "bitrate": remote_config["video"]["bitrate"], 
99
                    "codec": remote_config["video"]["codec"], 
100
                },
101
                
102
                "audio": {
103
                    # Decided by both:
104
                    "enabled": self.app.config.audio_send_enabled and remote_config["audio"]["recv_enabled"],
105

    
106
                    # Decided locally:
107
                    "source": self.app.config.audio_source,
108
                    "vumeter-id": self.app.gui.audio_levels_input_socket_id,
109
                    "buffer": self.app.config.audio_input_buffer,
110

    
111
                    # Decided by remote peer:
112
                    "numchannels": remote_config["audio"]["numchannels"],
113
                    "codec": remote_config["audio"]["codec"],
114
                    "port": remote_config["audio"]["port"], 
115
                    "synchronized": remote_config["audio"]["synchronized"],
116
                }, 
117
                "midi": {
118
                    "enabled": midi_send_enabled,
119
                    "input_device": midi_input_device,
120
                    "port": remote_config["midi"]["port"]
121
                }
122
            },
123
            # -------------------- recv ------------
124
            "receive": {
125
                "video": {
126
                    # Decided by both:
127
                    "enabled": self.app.config.video_recv_enabled and remote_config["video"]["send_enabled"],
128

    
129
                    # decided locally:
130
                    "sink": self.app.config.video_sink,
131
                    "port": str(self.app.recv_video_port), #decided by the app
132
                    "codec": self.app.config.video_codec,
133
                    "deinterlace": self.app.config.video_deinterlace, # bool
134
                    "window-title": "\"From %s\"" % (contact_name), #TODO: i18n
135
                    "jitterbuffer": self.app.config.video_jitterbuffer, 
136
                    "fullscreen": self.app.config.video_fullscreen, # bool
137
                    "display": self.app.config.video_display,
138
                    "bitrate": self.app.config.video_bitrate, # float
139
                    
140
                    # Decided by remote peer:
141
                    "aspect-ratio": remote_config["video"]["aspect_ratio"],
142
                    "width": int(receive_width), # int
143
                    "height": int(receive_height), # int
144
                },
145
                "audio": {
146
                    # Decided by both:
147
                    "enabled": self.app.config.audio_recv_enabled and remote_config["audio"]["send_enabled"],
148
                    
149
                    # decided locally:
150
                    "numchannels": self.app.config.audio_channels, # int
151
                    "vumeter-id": self.app.gui.audio_levels_output_socket_id,
152
                    "codec": self.app.config.audio_codec, 
153
                    "port": self.app.recv_audio_port,
154
                    "sink": self.app.config.audio_sink,
155
                    "buffer": self.app.config.audio_output_buffer,
156
                    "synchronized": self.app.config.audio_video_synchronized,
157
                    "jitterbuffer": audio_jitterbuffer
158
                },
159
                "midi": {
160
                    "enabled": midi_recv_enabled,
161
                    "jitterbuffer": self.app.config.midi_jitterbuffer,
162
                    "output_device": midi_output_device,
163
                    "port": str(self.app.recv_midi_port),
164
                }
165
            }
166
        }
167
        if self.session_details["send"]["video"]["source"] != "v4l2src":
168
            self.session_details["send"]["video"]["device"] = None
169
        if self.session_details["send"]["video"]["codec"] == "theora":
170
            self.session_details["send"]["video"]["bitrate"] = None
171
        print(str(self.session_details))
172

    
173
    def _prepare_stats_and_errors_dicts(self):
174
        # setting up
175
        self.rtcp_stats = {
176
            "send": {
177
                "video": {
178
                    "packets-lost": 0,
179
                    "packets-sent": 0,
180
                    "packet-loss-percent": 0.0,
181
                    "jitter": 0,
182
                    "bitrate": None,
183
                    "connected": False
184
                },
185
                "audio": {
186
                    "packets-lost": 0,
187
                    "packets-sent": 0,
188
                    "packet-loss-percent": 0.0,
189
                    "jitter": 0,
190
                    "bitrate": None,
191
                    "connected": False
192
                }
193
            },
194
            "receive": {
195
                "video": {
196
                    "connected": False,
197
                    "bitrate": None 
198
                },
199
                "audio": {
200
                    "connected": False,
201
                    "bitrate": None 
202
                }
203
            }
204
        }
205
        self.error_messages = {
206
            "send": [], # list of strings
207
            "receive": [], # list of strings
208
            }
209
        
210
    def start(self, host):
211
        """
212
        Starts the sender and receiver processes.
213
        
214
        @param host: str ip addr
215
        Raises a RuntimeError if a sesison is already in progress.
216
        """
217
        if self.state != process.STATE_STOPPED:
218
            raise RuntimeError("Cannot start streamers since they are %s." % (self.state)) # the programmer has done something wrong if we're here.
219
        
220
        self._gather_config_to_stream(host)
221
        details = self.session_details
222
        send_video_enabled = self.session_details["send"]["video"]["enabled"]
223
        send_audio_enabled = self.session_details["send"]["audio"]["enabled"]
224
        recv_video_enabled = self.session_details["receive"]["video"]["enabled"]
225
        recv_audio_enabled = self.session_details["receive"]["audio"]["enabled"]
226
        midi_recv_enabled = self.session_details["receive"]["midi"]["enabled"]
227
        midi_send_enabled = self.session_details["send"]["midi"]["enabled"]
228
        extra_send_enabled = not self.session_details["send"]["audio"]["synchronized"] and send_audio_enabled
229
        extra_recv_enabled = not self.session_details["receive"]["audio"]["synchronized"] and recv_audio_enabled
230
        normal_recv_enabled = recv_video_enabled or recv_audio_enabled and not extra_recv_enabled # if the self.sender and self.receiver are enabled
231
        normal_send_enabled = send_video_enabled or send_audio_enabled and not extra_send_enabled
232
        if not send_audio_enabled and not send_video_enabled and not midi_send_enabled and not midi_recv_enabled and not recv_audio_enabled and not recv_video_enabled:
233
            raise RuntimeError("Everything is disabled, but the application is trying to start to stream. This should not happen.") # the programmer has done a mistake if we're here.
234

    
235
        print "send_video_enabled", send_video_enabled
236
        print "send_audio_enabled", send_audio_enabled
237
        print "recv_video_enabled", recv_video_enabled
238
        print "recv_audio_enabled", recv_audio_enabled
239
        print "midi_recv_enabled", midi_recv_enabled 
240
        print "midi_send_enabled", midi_send_enabled
241

    
242
        # we store the arguments in lists
243
        milhouse_send_cmd_common = []
244
        milhouse_send_cmd_audio = []
245
        milhouse_send_cmd_video = []
246
        milhouse_recv_cmd_common = []
247
        milhouse_recv_cmd_audio = []
248
        milhouse_recv_cmd_video = []
249

    
250
        # ------------------ send ---------------
251
        # every element in the lists must be strings since we join them .
252
        # int elements are converted to str.
253
        milhouse_send_cmd_common.extend([
254
            "milhouse", 
255
            '--sender', 
256
            '--address', details["peer"]["address"]
257
            ])
258

    
259
        # send video:
260
        if send_video_enabled:
261
            milhouse_send_cmd_video.extend([
262
                '--videosource', details["send"]["video"]["source"],
263
                '--videocodec', details["send"]["video"]["codec"],
264
                '--videoport', str(details["send"]["video"]["port"]),
265
                '--width', str(details["send"]["video"]["width"]), 
266
                '--height', str(details["send"]["video"]["height"]),
267
                '--aspect-ratio', str(details["send"]["video"]["aspect-ratio"]),
268
                ])
269
            if details["send"]["video"]["source"] == "v4l2src":
270
                dev = self.app.parse_v4l2_device_name(details["send"]["video"]["device"])
271
                if dev is None:
272
                    print "v4l2 device is not found !!!!"
273
                else:
274
                    v4l2_dev_name = dev["name"]
275
                milhouse_send_cmd_video.extend(["--videodevice", v4l2_dev_name])
276
            if details["send"]["video"]["codec"] != "theora":
277
                milhouse_send_cmd_video.extend(['--videobitrate', str(int(details["send"]["video"]["bitrate"] * 1000000))])
278

    
279
        # send audio:
280
        if send_audio_enabled:
281
            milhouse_send_cmd_audio.extend([
282
                '--audiosource', details["send"]["audio"]["source"],
283
                '--numchannels', str(details["send"]["audio"]["numchannels"]),
284
                '--audiocodec', details["send"]["audio"]["codec"],
285
                '--audioport', str(details["send"]["audio"]["port"]),
286
                '--vumeter-id', str(details["send"]["audio"]["vumeter-id"]),
287
                '--audio-buffer', str(details["send"]["audio"]["buffer"])
288
                ])
289

    
290
        # ------------------- recv ----------------
291
        milhouse_recv_cmd_common.extend([
292
            "milhouse",
293
            '--receiver', 
294
            '--address', details["peer"]["address"]
295
            ])
296

    
297
        # recv video:
298
        if recv_video_enabled:
299
            milhouse_recv_cmd_video.extend([
300
                '--videosink', details["receive"]["video"]["sink"],
301
                '--videocodec', details["receive"]["video"]["codec"],
302
                '--videoport', str(details["receive"]["video"]["port"]),
303
                '--jitterbuffer', str(details["receive"]["video"]["jitterbuffer"]),
304
                '--width', str(details["receive"]["video"]["width"]),
305
                '--height', str(details["receive"]["video"]["height"]),
306
                '--aspect-ratio', details["receive"]["video"]["aspect-ratio"],
307
                '--window-title', details["receive"]["video"]["window-title"],
308
                '--display', details["receive"]["video"]["display"],
309
                ])
310
            if details["receive"]["video"]["fullscreen"]:
311
                milhouse_recv_cmd_video.append('--fullscreen')
312
            if details["receive"]["video"]["deinterlace"]:
313
                milhouse_recv_cmd_video.append('--deinterlace')
314

    
315
        # recv audio:
316
        if recv_audio_enabled:
317
            milhouse_recv_cmd_audio.extend([
318
                # audio:
319
                '--audiosink', details["receive"]["audio"]["sink"],
320
                '--numchannels', str(details["receive"]["audio"]["numchannels"]),
321
                '--audiocodec', details["receive"]["audio"]["codec"],
322
                '--audioport', str(details["receive"]["audio"]["port"]),
323
                '--vumeter-id', str(details["receive"]["audio"]["vumeter-id"]),
324
                '--audio-buffer', str(details["receive"]["audio"]["buffer"])
325
                ])
326

    
327
        self._prepare_stats_and_errors_dicts()
328
        # every element in the lists must be strings since we join them 
329
        # TODO: not sync
330
        # TODO: if both disabled, do not start sender/receiver
331
        # ---- audio/video receiver ----
332
        if recv_audio_enabled or recv_video_enabled: # receiver(s)
333
            milhouse_recv_cmd_final = []
334
            milhouse_recv_cmd_extra = []
335
            
336
            milhouse_recv_cmd_final.extend(milhouse_recv_cmd_common)
337
            milhouse_recv_cmd_extra.extend(milhouse_recv_cmd_common)
338
            milhouse_recv_cmd_final.extend(milhouse_recv_cmd_video)
339
            if extra_recv_enabled:
340
                milhouse_recv_cmd_extra.extend(milhouse_recv_cmd_audio)
341
                milhouse_recv_cmd_extra.extend(["--jitterbuffer", str(details["receive"]["audio"]["jitterbuffer"])])
342
            else:
343
                milhouse_recv_cmd_final.extend(milhouse_recv_cmd_audio)
344
            try:
345
                recv_cmd = " ".join(milhouse_recv_cmd_final)
346
            except TypeError, e:
347
                print e
348
                print milhouse_recv_cmd_final
349
                raise
350
            try:
351
                extra_recv_cmd = " ".join(milhouse_recv_cmd_extra)
352
            except TypeError, e:
353
                print e
354
                print milhouse_recv_cmd_extra
355
                raise
356
            if normal_recv_enabled:
357
                self.receiver = process.ProcessManager(command=recv_cmd, identifier="receiver")
358
                self.receiver.state_changed_signal.connect(self.on_process_state_changed)
359
                self.receiver.stdout_line_signal.connect(self.on_receiver_stdout_line)
360
                self.receiver.stderr_line_signal.connect(self.on_receiver_stderr_line)
361
            if extra_recv_enabled:
362
                self.extra_receiver = process.ProcessManager(command=extra_recv_cmd, identifier="extra_receiver")
363
                self.extra_receiver.state_changed_signal.connect(self.on_process_state_changed)
364
                self.extra_receiver.stdout_line_signal.connect(self.on_receiver_stdout_line)
365
                self.extra_receiver.stderr_line_signal.connect(self.on_receiver_stderr_line)
366
        # ---- audio/video sender ----
367
        if send_audio_enabled or send_video_enabled: # sender(s)
368
            milhouse_send_cmd_final = []
369
            milhouse_send_cmd_extra = []
370
            
371
            milhouse_send_cmd_final.extend(milhouse_send_cmd_common)
372
            milhouse_send_cmd_extra.extend(milhouse_send_cmd_common) # only used if we have to
373
            milhouse_send_cmd_final.extend(milhouse_send_cmd_video)
374
            if extra_send_enabled:
375
                milhouse_send_cmd_extra.extend(milhouse_send_cmd_audio)
376
            else:
377
                milhouse_send_cmd_final.extend(milhouse_send_cmd_audio)
378
            send_cmd = " ".join(milhouse_send_cmd_final)
379
            extra_send_cmd = " ".join(milhouse_send_cmd_extra)
380
            if normal_send_enabled:
381
                self.sender = process.ProcessManager(command=send_cmd, identifier="sender")
382
                self.sender.state_changed_signal.connect(self.on_process_state_changed)
383
                self.sender.stdout_line_signal.connect(self.on_sender_stdout_line)
384
                self.sender.stderr_line_signal.connect(self.on_sender_stderr_line)
385
            if extra_send_enabled:
386
                self.extra_sender = process.ProcessManager(command=extra_send_cmd, identifier="extra_sender")
387
                self.extra_sender.state_changed_signal.connect(self.on_process_state_changed)
388
                self.extra_sender.stdout_line_signal.connect(self.on_sender_stdout_line)
389
                self.extra_sender.stderr_line_signal.connect(self.on_sender_stderr_line)
390
                # FIXME: too much code duplication
391
        
392
        if midi_recv_enabled:
393
            midi_out_device = self.app.parse_midi_device_name(details["receive"]["midi"]["output_device"], is_input=False)
394
            #TODO: check if is None
395
            midi_recv_args = [
396
                "midistream",
397
                "--address", details["peer"]["address"],
398
                "--receiving-port", str(details["receive"]["midi"]["port"]),
399
                "--jitter-buffer", str(details["receive"]["midi"]["jitterbuffer"]),
400
                "--output-device", str(midi_out_device["number"])
401
                ]
402
                #"--verbose",
403
            midi_recv_command = " ".join(midi_recv_args) 
404
            self.midi_receiver = process.ProcessManager(command=midi_recv_command, identifier="midi_receiver")
405
            self.midi_receiver.state_changed_signal.connect(self.on_process_state_changed)
406
            self.midi_receiver.stdout_line_signal.connect(self.on_midi_stdout_line)
407
            self.midi_receiver.stderr_line_signal.connect(self.on_midi_stderr_line)
408
        
409
        if midi_send_enabled:
410
            midi_in_device = self.app.parse_midi_device_name(details["send"]["midi"]["input_device"], is_input=True)
411
            #TODO: check if is None
412
            midi_send_args = [
413
                "midistream",
414
                "--address", details["peer"]["address"],
415
                "--sending-port", str(details["send"]["midi"]["port"]),
416
                "--input-device", str(midi_in_device["number"])
417
                ]
418
                #"--verbose",
419
            midi_send_command = " ".join(midi_send_args) 
420
            self.midi_sender = process.ProcessManager(command=midi_send_command, identifier="midi_sender")
421
            self.midi_sender.state_changed_signal.connect(self.on_process_state_changed)
422
            self.midi_sender.stdout_line_signal.connect(self.on_midi_stdout_line)
423
            self.midi_sender.stderr_line_signal.connect(self.on_midi_stderr_line)
424
        
425
        # starting
426
        self._set_state(process.STATE_STARTING)
427
        if send_audio_enabled or send_video_enabled:
428
            print "$", send_cmd
429
            if normal_send_enabled:
430
                self.sender.start()
431
            if extra_send_enabled:
432
                self.extra_sender.start()
433
        if recv_audio_enabled or recv_video_enabled:
434
            print "$", recv_cmd
435
            if normal_recv_enabled:
436
                self.receiver.start()
437
            if extra_recv_enabled:
438
                self.extra_receiver.start()
439
        if midi_recv_enabled:
440
            self.midi_receiver.start()
441
        if midi_send_enabled:
442
            self.midi_sender.start()
443

    
444
    def get_command_lines(self):
445
        """
446
        Returns a list of all the current processes command lines.
447
        @rettype: list
448
        """
449
        ret = []
450
        for proc in self.get_all_streamer_process_managers():
451
            ret.append(proc.command)
452
        return ret
453

    
454
    def on_midi_stdout_line(self, process_manager, line):
455
        print process_manager.identifier, line
456

    
457
    def on_midi_stderr_line(self, process_manager, line):
458
        print process_manager.identifier, line
459

    
460
    def on_receiver_stdout_line(self, process_manager, line):
461
        """
462
        Handles a new line from our receiver process' stdout
463
        """
464
        if "stream connected" in line:
465
            if "audio" in line:
466
                self.rtcp_stats["receive"]["audio"]["connected"] = True
467
            elif "video" in line:
468
                self.rtcp_stats["receive"]["video"]["connected"] = True
469
        elif "BITRATE" in line:
470
            if "video" in line:
471
                self.rtcp_stats["receive"]["video"]["bitrate"] = int(line.split(":")[-1])
472
            elif "audio" in line:
473
                self.rtcp_stats["receive"]["audio"]["bitrate"] = int(line.split(":")[-1])
474
        else:
475
            print "%9s stdout: %s" % (process_manager.identifier, line)
476

    
477
    def on_receiver_stderr_line(self, process_manager, line):
478
        """
479
        Handles a new line from our receiver process' stderr
480
        """
481
        print "%9s stderr: %s" % (process_manager.identifier, line)
482
        if "CRITICAL" in line or "ERROR" in line:
483
            self.error_messages["receive"].append(line)
484
    
485
    def on_sender_stdout_line(self, process_manager, line):
486
        """
487
        Handles a new line from our receiver process' stdout
488
        """
489
        print "%9s stdout: %s" % (process_manager.identifier, line)
490
        try:
491
            if "PACKETS-LOST" in line:
492
                if "video" in line:
493
                    self.rtcp_stats["send"]["video"]["packets-lost"] = int(line.split(":")[-1])
494
                elif "audio" in line:
495
                    self.rtcp_stats["send"]["audio"]["packets-lost"] = int(line.split(":")[-1])
496
                #self._calculate_packet_loss()
497
            elif "AVERAGE PACKET-LOSS" in line:
498
                if "video" in line:
499
                    self.rtcp_stats["send"]["video"]["packet-loss-percent"] = int(line.split(":")[-1])
500
                elif "audio" in line:
501
                    self.rtcp_stats["send"]["audio"]["packet-loss-percent"] = int(line.split(":")[-1])
502
            elif "PACKETS-SENT" in line:
503
                if "video" in line:
504
                    self.rtcp_stats["send"]["video"]["packets-sent"] = int(line.split(":")[-1])
505
                elif "audio" in line:
506
                    self.rtcp_stats["send"]["audio"]["packets-sent"] = int(line.split(":")[-1])
507
                #self._calculate_packet_loss()
508
            elif "JITTER" in line:
509
                if "video" in line:
510
                    self.rtcp_stats["send"]["video"]["jitter"] = int(line.split(":")[-1])
511
                elif "audio" in line:
512
                    self.rtcp_stats["send"]["audio"]["jitter"] = int(line.split(":")[-1])
513
            elif "BITRATE" in line:
514
                if "video" in line:
515
                    self.rtcp_stats["send"]["video"]["bitrate"] = int(line.split(":")[-1])
516
                elif "audio" in line:
517
                    self.rtcp_stats["send"]["audio"]["bitrate"] = int(line.split(":")[-1])
518
            elif "connected" in line:
519
                if "video" in line:
520
                    self.rtcp_stats["send"]["video"]["connected"] = True
521
                elif "audio" in line:
522
                    self.rtcp_stats["send"]["audio"]["connected"] = True
523
        except ValueError, e:
524
            print(e)
525

    
526
    def on_sender_stderr_line(self, process_manager, line):
527
        """
528
        Handles a new line from our receiver process' stderr
529
        """
530
        print "%9s stderr: %s" % (process_manager.identifier, line)
531
        if "CRITICAL" in line or "ERROR" in line:
532
            self.error_messages["send"].append(line)
533

    
534
    def is_busy(self):
535
        """
536
        Retuns True if a streaming session is in progress.
537
        """
538
        return self.state != process.STATE_STOPPED
539

    
540
    def get_all_streamer_process_managers(self):
541
        """
542
        Returns all the current streaming process managers for the current session.
543
        @rtype: list
544
        """
545
        ret = []
546
        for proc in [self.receiver, self.extra_receiver, self.sender, self.extra_sender, self.midi_receiver, self.midi_sender]:
547
            if proc is not None:
548
                ret.append(proc)
549
        return ret
550

    
551
    def on_process_state_changed(self, process_manager, process_state):
552
        """
553
        Slot for the ProcessManager.state_changed_signal
554
        Calls stop() if one of the processes crashed.
555
        """
556
        print process_manager, process_state
557
        if process_state == process.STATE_RUNNING:
558
            # As soon as one is running, set our state to running
559
            if self.state == process.STATE_STARTING:
560
                self._set_state(process.STATE_RUNNING)
561
        elif process_state == process.STATE_STOPPING:
562
            pass
563
        elif process_state == process.STATE_STARTING:
564
            pass
565
        elif process_state == process.STATE_STOPPED:
566
            # As soon as one crashes or is not able to start, stop all streamer processes.
567
            if self.state in [process.STATE_RUNNING, process.STATE_STARTING]:
568
                print("A streamer process died. Stopping the local streamer manager.")
569
                self.stop() # sets self.state to STOPPING
570
            # Next, if all streamers are dead, we can say this manager is stopped
571
            if self.state == process.STATE_STOPPING:
572
                one_is_left = False
573
                for proc in self.get_all_streamer_process_managers():
574
                    if process_manager is not proc and proc.state != process.STATE_STOPPED:
575
                        print("Streamer process %s is not dead, so we are not done stopping. Its state is %s." % (proc, proc.state))
576
                        one_is_left = True
577
                if not one_is_left:
578
                    print "Setting streamers manager to STOPPED"
579
                    self._set_state(process.STATE_STOPPED)
580

    
581
    def on_stopped(self):
582
        """
583
        When the state changes to stopped, 
584
         * check for errors and display them to the user.
585
        """
586
        msg = ""
587
        details = ""
588
        show_error_dialog = False
589
        #TODO: internationalize
590
        print("All streamers are stopped.")
591
        print("Error messages for this session: %s" % (self.error_messages))
592
        if len(self.error_messages["send"]) != 0:
593
            details += _("Errors from local sender:") + "\n"
594
            for line in self.error_messages["send"]:
595
                details += " * " + line + "\n"
596
            show_error_dialog = True
597
        if len(self.error_messages["receive"]) != 0:
598
            details += _("Errors from local receiver:") + "\n"
599
            for line in self.error_messages["receive"]:
600
                details += " * " + line + "\n"
601
            show_error_dialog = True
602
        if show_error_dialog:
603
            msg = _("Some errors occured during the audio/video streaming session.")
604
            dialogs.ErrorDialog.create(msg, parent=self.app.gui.main_window, details=details)
605
        # TODO: should we set all our process managers to None
606

    
607
        # set all to None:
608
        self.sender = None
609
        self.receiver = None
610
        self.extra_sender = None
611
        self.extra_receiver = None
612
        self.midi_receiver = None
613
        self.midi_sender = None
614
        print "should all be None:", self.get_all_streamer_process_managers()
615
    
616
    def _set_state(self, new_state):
617
        """
618
        Handles state changes.
619
        """
620
        if self.state != new_state:
621
            self.state_changed_signal(self, new_state)
622
            self.state = new_state
623
            if new_state == process.STATE_STOPPED:
624
                self.on_stopped()
625
        else:
626
            raise RuntimeError("Setting state to %s, which is already the current state." % (self.state))
627
            
628
    def stop(self):
629
        """
630
        Stops the sender and receiver processes.
631
        Does not send any message to the remote peer ! This must be done somewhere else.
632
        """
633
        #TODO: return a deferred
634
        # stopping
635
        if self.state in [process.STATE_RUNNING, process.STATE_STARTING]:
636
            self._set_state(process.STATE_STOPPING)
637
            for proc in self.get_all_streamer_process_managers():
638
                if proc is not None:
639
                    if proc.state != process.STATE_STOPPED and proc.state != process.STATE_STOPPING:
640
                        proc.stop()