Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
Maff | 0b7849b016 | |
Maff | c86ec2c23c | |
Matthew Connelly | afb2ac83b6 | |
Matthew Connelly | 8ff683ef6d | |
Matthew Connelly | 16dad09773 | |
Matthew Connelly | 0e8e5b5be5 | |
Matthew Connelly | f5e5f710d4 | |
Matthew Connelly | c06c725a7e | |
Matthew Connelly | aab1198ec2 | |
Matthew Connelly | eecf8ed026 | |
Matthew Connelly | 0e4bc692ab |
29
README.md
29
README.md
|
@ -11,26 +11,26 @@ Any incoming call will immediately be answered, regardless of the user segment o
|
||||||
|
|
||||||
As noted above, this application was written to work in tandem with 3CX. As such, installation notes are geared towards the 3CX distribution of Debian Linux 9.
|
As noted above, this application was written to work in tandem with 3CX. As such, installation notes are geared towards the 3CX distribution of Debian Linux 9.
|
||||||
The general process is as follows:
|
The general process is as follows:
|
||||||
* apt update
|
* `apt update`
|
||||||
* apt install -y python-pjproject
|
* `apt install -y python-pjproject`
|
||||||
* wget https://git.maff.scot/maff/trashtalker/archive/v1.1.tar.gz
|
* `wget https://git.maff.scot/maff/trashtalker/archive/v1.1.tar.gz`
|
||||||
* tar xaf v1.1.tar.gz
|
* `tar xaf v1.1.tar.gz`
|
||||||
* rm v1.1.tar.gz
|
* `rm v1.1.tar.gz`
|
||||||
* cd trashtalker
|
* `cd trashtalker`
|
||||||
* mv trashtalker.py /usr/local/bin/
|
* `mv trashtalker.py /usr/local/bin/`
|
||||||
* mv trashtalker@.service /etc/systemd/system/
|
* `mv trashtalker@.service /etc/systemd/system/`
|
||||||
* mkdir /opt/.tt
|
* `mkdir /opt/.tt`
|
||||||
* mv example.conf /opt/.tt/
|
* `mv example.conf /opt/.tt/`
|
||||||
* modify the contents of example.conf to match your needs
|
* modify the contents of example.conf to match your needs
|
||||||
* systemctl enable trashtalker@example
|
* `systemctl enable trashtalker@example`
|
||||||
* service trashtalker@example start
|
* `service trashtalker@example start`
|
||||||
|
|
||||||
Within 3CX:
|
Within 3CX:
|
||||||
* Create a new SIP trunk (country: Generic, provider: Generic SIP Trunk, main no: any number of your choice, it doesn't matter)
|
* Create a new SIP trunk (country: Generic, provider: Generic SIP Trunk, main no: any number of your choice, it doesn't matter)
|
||||||
* Name the new trunk something of your choice
|
* Name the new trunk something of your choice
|
||||||
* Define the registrar and outbound proxy IPs as 127.0.0.1
|
* Define the registrar and outbound proxy IPs as `127.0.0.1`
|
||||||
* Set the port for both of these to match the particular instance of TrashTalker you're configuring
|
* Set the port for both of these to match the particular instance of TrashTalker you're configuring
|
||||||
* Leave the authentication settings to "Do not require - IP Based"
|
* Leave the authentication settings to "`Do not require - IP Based`"
|
||||||
* Click OK to save the trunk
|
* Click OK to save the trunk
|
||||||
* Create an outbound dial route with parameters of your preference, and set the first route to be the SIP trunk you created above. Ensure you do not set any other route entries for this outbound dial route.
|
* Create an outbound dial route with parameters of your preference, and set the first route to be the SIP trunk you created above. Ensure you do not set any other route entries for this outbound dial route.
|
||||||
* Click OK to save the rule
|
* Click OK to save the rule
|
||||||
|
@ -40,3 +40,4 @@ Within 3CX:
|
||||||
|
|
||||||
This application currently operates the PR Gnusline, which can be dialled at the following number(s):
|
This application currently operates the PR Gnusline, which can be dialled at the following number(s):
|
||||||
* +44 (0) 1337 515 404
|
* +44 (0) 1337 515 404
|
||||||
|
* +1 (412) 406-9141
|
||||||
|
|
292
trashtalker.py
292
trashtalker.py
|
@ -1,8 +1,12 @@
|
||||||
#!/usr/bin/python2.7
|
#!/usr/bin/python2.7
|
||||||
import sys
|
import sys
|
||||||
|
#It shouldn't be a surprise that pjsua wouldn't be available on the local machine.
|
||||||
|
#pylint: disable=import-error
|
||||||
import pjsua as pj
|
import pjsua as pj
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from os import listdir, getenv
|
from os import listdir, getenv
|
||||||
|
#It also shouldn't be a surprise that certain members of the signal library wouldn't be available on certain OSes (Windows).
|
||||||
|
#pylint: disable=no-name-in-module
|
||||||
from signal import signal, SIGHUP, SIGINT, SIGTERM
|
from signal import signal, SIGHUP, SIGINT, SIGTERM
|
||||||
from random import shuffle
|
from random import shuffle
|
||||||
|
|
||||||
|
@ -21,46 +25,118 @@ from random import shuffle
|
||||||
## Please also be aware that, by default, playlist length is limited to 64 items. I can find no reason
|
## Please also be aware that, by default, playlist length is limited to 64 items. I can find no reason
|
||||||
## for this limitation, and it is specific to the python bindings for the PJSUA library.
|
## for this limitation, and it is specific to the python bindings for the PJSUA library.
|
||||||
## If you'd like to have a playlist longer than 64 items, you will need to recompile python-pjsua
|
## If you'd like to have a playlist longer than 64 items, you will need to recompile python-pjsua
|
||||||
## with the appropriate adjustment to _pjsua.c line 2515
|
## with the appropriate adjustment to pjsip-apps/src/python/_pjsua.c line 2515, in the definition for
|
||||||
|
## PyObject py_pjsua_playlist_create(self, args): pj_str_t files[64];
|
||||||
|
## A possible idea would be to make this configurable.
|
||||||
##
|
##
|
||||||
## If you can get this working using PJSUA2, a pull request would be greatly appreciated.
|
## If you can get this working using PJSUA2, a pull request would be greatly appreciated.
|
||||||
|
|
||||||
# Utility classes, used basically as enums or generics
|
# Utility classes, used basically as enums or generics
|
||||||
class State(object):
|
class State:
|
||||||
|
class States:
|
||||||
|
done=1
|
||||||
|
preinit=4
|
||||||
|
media_init=8
|
||||||
|
init=16
|
||||||
|
lib=None
|
||||||
running=False
|
running=False
|
||||||
class PJStates:
|
status=0
|
||||||
init=0
|
sip_ringing=180
|
||||||
deinit=1
|
sip_answer=200
|
||||||
class SIPStates:
|
def Log(level, source, line, error=False):
|
||||||
ringing=180
|
pfx='*'
|
||||||
answer=200
|
if error:
|
||||||
|
pfx='!'
|
||||||
|
print("%s %s: %s" % (pfx*level, source, line))
|
||||||
|
sys.stdout.flush()
|
||||||
|
def preinit(self):
|
||||||
|
self.Log(1, "preinit", "initialising from environment")
|
||||||
|
self.status=States.preinit
|
||||||
|
self.LOG_LEVEL=int(getenv('TT_LOG_LEVEL', 0))
|
||||||
|
self.port=int(getenv('TT_LISTEN_PORT', 55060))
|
||||||
|
self.source=getenv('TT_MEDIA_SOURCE', '/opt/media/')
|
||||||
|
assert self.source.startswith('/'), "TT_MEDIA_SOURCE must specify an absolute path!"
|
||||||
|
self.status&=States.done
|
||||||
|
def init(self):
|
||||||
|
self.status=States.init
|
||||||
|
self.lib=pj.Lib()
|
||||||
|
self.cfg_ua=pj.UAConfig()
|
||||||
|
self.cfg_md=pj.MediaConfig()
|
||||||
|
self.cfg_ua.max_calls, self.cfg_ua.user_agent = 32, "TrashTalker/1.0"
|
||||||
|
self.cfg_md.no_vad, self.cfg_md.enable_ice = True, False
|
||||||
|
self.cfg_lg=pj.LogConfig(
|
||||||
|
level=self.LOG_LEVEL,
|
||||||
|
callback=PJLog)
|
||||||
|
self.lib.init(
|
||||||
|
ua_cfg=cfg_ua,
|
||||||
|
media_cfg=cfg_md,
|
||||||
|
log_cfg=cfg_lg)
|
||||||
|
self.lib.set_null_snd_dev()
|
||||||
|
self.lib.start(
|
||||||
|
with_thread=True)
|
||||||
|
self.transport=self.lib.create_transport(
|
||||||
|
pj.TransportType.UDP,
|
||||||
|
pj.TransportConfig(self.port))
|
||||||
|
self.account=self.lib.create_account_for_transport(
|
||||||
|
self.transport,
|
||||||
|
cb=AccountCb())
|
||||||
|
self.uri="sip:%s:%s" % (self.transport.info().host, self.transport.info().port)
|
||||||
|
self.status&=States.done
|
||||||
|
def media_init(self):
|
||||||
|
self.Log(3, "playlist-load", "loading playlist files from media path %s" % self.source)
|
||||||
|
self.status=States.media_init
|
||||||
|
if not self.source.endswith('/'):
|
||||||
|
self.Log(4, "playlist-load", "appending trailing / to TT_MEDIA_SOURCE")
|
||||||
|
self.source="%s/" % self.source
|
||||||
|
self.playlist=listdir(self.source)
|
||||||
|
self.playlist[:]=[self.source+file for file in self.playlist]
|
||||||
|
assert (len(self.playlist) > 1), "playlist path %s must contain more than one audio file" % self.source
|
||||||
|
self.Log(3, "playlist-load", "loaded %s media items from path %s" % (len(self.playlist), self.source))
|
||||||
|
self.status&=States.done
|
||||||
|
def deinit(self):
|
||||||
|
assert (self.status==(States.init & States.done)), "State.deinit cannot be called when not fully initialised"
|
||||||
|
self.lib.hangup_all()
|
||||||
|
self.lib.handle_events(timeout=250)
|
||||||
|
try:
|
||||||
|
self.account.delete()
|
||||||
|
self.lib.destroy()
|
||||||
|
self.lib=self.account=self.transport=None
|
||||||
|
except AttributeError:
|
||||||
|
self.Log(1, "deinit", "Got an AttributeError exception during shutdown.", error=True)
|
||||||
|
pass
|
||||||
|
except pj.Error as e:
|
||||||
|
self.Log(1, "deinit", "Got a PJError exception during shutdown: %s" % str(e), error=True)
|
||||||
|
pass
|
||||||
|
self.status=0
|
||||||
|
def run(self):
|
||||||
|
self.running=True
|
||||||
|
while self.running:
|
||||||
|
sleep(0.2)
|
||||||
|
def stop(self):
|
||||||
|
self.running=False
|
||||||
|
|
||||||
#Utility and state definitions
|
#Utility and state definitions
|
||||||
state=State()
|
state=State()
|
||||||
# Logging
|
# Logging
|
||||||
def PJLog(level, line, length):
|
def PJLog(level, line, length):
|
||||||
Log(level+1, "pjsip", line)
|
global state
|
||||||
def Log(level, source, line, error=False):
|
state.Log(level+1, "pjsip", line)
|
||||||
pfx='*'
|
|
||||||
if error:
|
|
||||||
pfx='!'
|
|
||||||
print("%s %s: %s" % (pfx*level, source, line))
|
|
||||||
sys.stdout.flush()
|
|
||||||
# Signal handling
|
# Signal handling
|
||||||
def sighandle(_signo, _stack_frame):
|
def sighandle(_signo, _stack_frame):
|
||||||
global state
|
global state
|
||||||
Log(1, "sighandler", "caught signal %s" % _signo)
|
state.Log(1, "sighandler", "caught signal %s" % _signo)
|
||||||
if _signo == 1:
|
if _signo == 1:
|
||||||
#SIGHUP
|
#SIGHUP
|
||||||
Log(1, "sighandler", "SIGHUP invoked playlist reload")
|
state.Log(1, "sighandler", "SIGHUP invoked playlist reload")
|
||||||
MediaLoadPlaylist()
|
state.media_init()
|
||||||
elif _signo == 2:
|
elif _signo == 2:
|
||||||
#SIGINT
|
#SIGINT
|
||||||
Log(1, "sighandler", "SIGINT invoked current call flush")
|
state.Log(1, "sighandler", "SIGINT invoked current call flush")
|
||||||
state.lib.hangup_all()
|
state.lib.hangup_all()
|
||||||
elif _signo == 15:
|
elif _signo == 15:
|
||||||
#SIGTERM
|
#SIGTERM
|
||||||
Log(1, "sighandler", "SIGTERM invoked app shutdown")
|
state.Log(1, "sighandler", "SIGTERM invoked app shutdown")
|
||||||
state.running=False
|
state.stop()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
@ -70,146 +146,124 @@ class AccountCb(pj.AccountCallback):
|
||||||
pj.AccountCallback.__init__(self, account)
|
pj.AccountCallback.__init__(self, account)
|
||||||
|
|
||||||
def on_incoming_call(self, call):
|
def on_incoming_call(self, call):
|
||||||
Log(2, "event-call-in", "caller %s dialled in" % call.info().remote_uri)
|
global state
|
||||||
|
state.Log(2, "event-call-in", "caller %s dialled in" % call.info().remote_uri)
|
||||||
call.set_callback(CallCb(call))
|
call.set_callback(CallCb(call))
|
||||||
#Answer call with SIP/2.0 180 RINGING
|
#Answer call with SIP/2.0 180 RINGING
|
||||||
#This kicks in the EARLY media state, allowing us to initialise the playlist before the call connects
|
#This kicks in the EARLY media state, allowing us to initialise the playlist before the call connects
|
||||||
call.answer(SIPStates.ringing)
|
call.answer(state.sip_ringing)
|
||||||
# Call Callback class
|
# Call Callback class
|
||||||
class CallCb(pj.CallCallback):
|
class CallCb(pj.CallCallback):
|
||||||
def __init__(self, call=None):
|
def __init__(self, call=None):
|
||||||
pj.CallCallback.__init__(self, call)
|
pj.CallCallback.__init__(self, call)
|
||||||
|
|
||||||
|
def create_media(self):
|
||||||
|
global state
|
||||||
|
self.playlist=state.playlist
|
||||||
|
shuffle(self.playlist)
|
||||||
|
self.instmedia=state.lib.create_playlist(
|
||||||
|
loop=True, filelist=self.playlist, label="trashtalklist")
|
||||||
|
self.slotmedia=state.lib.playlist_get_slot(self.instmedia)
|
||||||
|
state.Log(4, "call-media-create", "created playlist for current call")
|
||||||
|
def connect_media(self):
|
||||||
|
global state
|
||||||
|
self.slotcall=self.call.info().conf_slot
|
||||||
|
state.lib.conf_connect(self.slotmedia, self.slotcall)
|
||||||
|
state.Log(4, "call-media-connect", "connected playlist media endpoint to call")
|
||||||
|
def disconnect_media(self):
|
||||||
|
global state
|
||||||
|
state.lib.conf_disconnect(self.slotmedia, self.slotcall)
|
||||||
|
state.Log(4, "call-media-disconnect", "disconnected playlist media endpoint from call")
|
||||||
|
def destroy_media(self):
|
||||||
|
global state
|
||||||
|
state.lib.playlist_destroy(self.instmedia)
|
||||||
|
self.instmedia=None
|
||||||
|
self.slotmedia=None
|
||||||
|
self.playlist=None
|
||||||
|
state.Log(4, "call-media-destroy", "destroyed playlist endpoint and object")
|
||||||
|
|
||||||
def on_state(self):
|
def on_state(self):
|
||||||
global state
|
global state
|
||||||
Log(2, "event-state-change", "SIP/2.0 %s (%s), call %s in call with party %s" %
|
state.Log(2, "event-state-change", "SIP/2.0 %s (%s), call %s in call with party %s" %
|
||||||
(self.call.info().last_code, self.call.info().last_reason,
|
(self.call.info().last_code, self.call.info().last_reason,
|
||||||
self.call.info().state_text, self.call.info().remote_uri))
|
self.call.info().state_text, self.call.info().remote_uri))
|
||||||
#EARLY media state allows us to init the playlist while the call establishes
|
#EARLY media state allows us to init the playlist while the call establishes
|
||||||
if self.call.info().state == pj.CallState.EARLY:
|
if self.call.info().state == pj.CallState.EARLY:
|
||||||
self.playlist=state.playlist
|
self.create_media()
|
||||||
shuffle(self.playlist)
|
state.Log(3, "event-call-state-early", "initialised new trashtalk playlist instance")
|
||||||
self.instmedia=state.lib.create_playlist(
|
|
||||||
loop=True, filelist=self.playlist, label="trashtalklist")
|
|
||||||
self.slotmedia=state.lib.playlist_get_slot(self.instmedia)
|
|
||||||
Log(3, "event-call-state-early", "initialised new trashtalk playlist instance")
|
|
||||||
#answer the call once playlist is prepared
|
#answer the call once playlist is prepared
|
||||||
self.call.answer(SIPStates.answer)
|
self.call.answer(state.sip_answer)
|
||||||
#CONFIRMED state indicates the call is connected
|
#CONFIRMED state indicates the call is connected
|
||||||
elif self.call.info().state == pj.CallState.CONFIRMED:
|
elif self.call.info().state == pj.CallState.CONFIRMED:
|
||||||
Log(3, "event-call-state-confirmed", "answered call")
|
state.Log(3, "event-call-state-confirmed", "answered call")
|
||||||
self.slotcall=self.call.info().conf_slot
|
self.connect_media()
|
||||||
state.lib.conf_connect(self.slotmedia, self.slotcall)
|
state.Log(3, "event-call-conf-joined", "joined trashtalk to call")
|
||||||
Log(3, "event-call-conf-joined", "joined trashtalk to call")
|
|
||||||
#DISCONNECTED state indicates the call has ended (whether on our end or the caller's)
|
#DISCONNECTED state indicates the call has ended (whether on our end or the caller's)
|
||||||
elif self.call.info().state == pj.CallState.DISCONNECTED:
|
elif self.call.info().state == pj.CallState.DISCONNECTED:
|
||||||
Log(3, "event-call-state-disconnected", "call disconnected")
|
state.Log(3, "event-call-state-disconnected", "call disconnected")
|
||||||
state.lib.conf_disconnect(self.slotmedia, self.slotcall)
|
self.disconnect_media()
|
||||||
state.lib.playlist_destroy(self.instmedia)
|
self.destroy_media()
|
||||||
Log(3, "event-call-conf-left", "removed trashtalk instance from call and destroyed it")
|
state.Log(3, "event-call-conf-left", "removed trashtalk instance from call and destroyed it")
|
||||||
|
|
||||||
|
def on_dtmf_digit(self, digit):
|
||||||
|
global state
|
||||||
|
state.Log(3, "dtmf-digit", "received DTMF signal: %s" % digit)
|
||||||
|
if digit == '*':
|
||||||
|
self.disconnect_media()
|
||||||
|
self.destroy_media()
|
||||||
|
self.create_media()
|
||||||
|
self.connect_media()
|
||||||
|
|
||||||
#I'm not sure what this is for, as all media handling is actually done within the SIP events above
|
#I'm not sure what this is for, as all media handling is actually done within the SIP events above
|
||||||
def on_media_state(self):
|
def on_media_state(self):
|
||||||
|
global state
|
||||||
if self.call.info().media_state == pj.MediaState.ACTIVE:
|
if self.call.info().media_state == pj.MediaState.ACTIVE:
|
||||||
Log(4, "event-media-state-change", "Media State transitioned to ACTIVE")
|
state.Log(4, "event-media-state-change", "Media State transitioned to ACTIVE")
|
||||||
else:
|
else:
|
||||||
Log(4, "event-media-state-change", "Media State transitioned to INACTIVE")
|
state.Log(4, "event-media-state-change", "Media State transitioned to INACTIVE")
|
||||||
|
|
||||||
# Main logic functions
|
# Main logic functions
|
||||||
def PJControl(action):
|
|
||||||
global state
|
|
||||||
if action == PJStates.init:
|
|
||||||
state.lib=pj.Lib()
|
|
||||||
state.cfg_ua=pj.UAConfig()
|
|
||||||
state.cfg_md=pj.MediaConfig()
|
|
||||||
state.cfg_ua.max_calls, state.cfg_ua.user_agent = 32, "TrashTalker/1.0"
|
|
||||||
state.cfg_md.no_vad, state.cfg_md.enable_ice = True, False
|
|
||||||
state.lib.init(
|
|
||||||
ua_cfg=state.cfg_ua,
|
|
||||||
media_cfg=state.cfg_md,
|
|
||||||
log_cfg=pj.LogConfig(
|
|
||||||
level=state.LOG_LEVEL,
|
|
||||||
callback=PJLog
|
|
||||||
)
|
|
||||||
)
|
|
||||||
state.lib.set_null_snd_dev()
|
|
||||||
state.lib.start(with_thread=True)
|
|
||||||
state.transport=state.lib.create_transport(
|
|
||||||
pj.TransportType.UDP,
|
|
||||||
pj.TransportConfig(state.port)
|
|
||||||
)
|
|
||||||
state.account=state.lib.create_account_for_transport(
|
|
||||||
state.transport,
|
|
||||||
cb=AccountCb()
|
|
||||||
)
|
|
||||||
state.uri="sip:%s:%s" % (state.transport.info().host, state.transport.info().port)
|
|
||||||
elif action == PJStates.deinit:
|
|
||||||
state.lib.hangup_all()
|
|
||||||
# allow time for cleanup before destroying objects
|
|
||||||
state.lib.handle_events(timeout=250)
|
|
||||||
try:
|
|
||||||
state.account.delete()
|
|
||||||
state.lib.destroy()
|
|
||||||
state.lib=state.account=state.transport=None
|
|
||||||
except AttributeError:
|
|
||||||
Log(1, "deinit", "AttributeError when clearing down pjsip, this is likely fine", error=True)
|
|
||||||
pass
|
|
||||||
except pj.Error as e:
|
|
||||||
Log(1, "deinit", "pjsip error when clearing down: %s" % str(e), error=True)
|
|
||||||
pass
|
|
||||||
|
|
||||||
def WaitLoop():
|
|
||||||
global state
|
|
||||||
while state.running:
|
|
||||||
sleep(0.2)
|
|
||||||
|
|
||||||
def MediaLoadPlaylist():
|
|
||||||
Log(3, "playlist-load", "loading playlist files")
|
|
||||||
global state
|
|
||||||
if not state.source.endswith('/'):
|
|
||||||
Log(4, "playlist-load", "appending trailing / to TT_MEDIA_SOURCE")
|
|
||||||
state.source="%s/" % state.source
|
|
||||||
state.playlist=listdir(state.source)
|
|
||||||
state.playlist[:]=[state.source+file for file in state.playlist]
|
|
||||||
assert (len(state.playlist) > 1), "playlist path %s must contain more than one audio file" % state.source
|
|
||||||
Log(3, "playlist-load",
|
|
||||||
"load playlist from %s, got %s files" % (state.source, len(state.playlist)))
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global state
|
global state
|
||||||
Log(1, "init", "initialising trashtalker")
|
#Try to pre-init
|
||||||
state.LOG_LEVEL=int(getenv('TT_LOG_LEVEL', 0))
|
try:
|
||||||
#TT_MEDIA_SOURCE and TT_LISTEN_PORT can be configured via env. variables
|
state.preinit()
|
||||||
state.source=getenv('TT_MEDIA_SOURCE', '/opt/media/')
|
assert (state.status==(State.States.preinit & State.States.done))
|
||||||
state.port=int(getenv('TT_LISTEN_PORT', 55060))
|
except AssertionError as e:
|
||||||
state.running=True
|
state.Log(1, "preinit", "AssertionError while pre-initialising: %s" % str(e))
|
||||||
|
raise Exception("Unable to start up TrashTalker. Check all configuration parameters are correct, and review logs.")
|
||||||
|
#Attach signal handlers
|
||||||
signal(SIGHUP, sighandle)
|
signal(SIGHUP, sighandle)
|
||||||
signal(SIGINT, sighandle)
|
signal(SIGINT, sighandle)
|
||||||
signal(SIGTERM, sighandle)
|
signal(SIGTERM, sighandle)
|
||||||
assert state.source.startswith('/'), "Environment variable TT_MEDIA_PATH must be an absolute path!"
|
#Try to initialise media
|
||||||
try:
|
try:
|
||||||
MediaLoadPlaylist()
|
state.media_init()
|
||||||
|
assert (state.status==(State.States.media_init & State.States.done))
|
||||||
except:
|
except:
|
||||||
Log(2, "playlist-load", "exception encountered while loading playlist from path %s" % state.source, error=True)
|
state.Log(2, "playlist-load", "exception encountered while loading playlist from path %s" % state.source, error=True)
|
||||||
raise Exception("Unable to load playlist")
|
raise Exception("Unable to load playlist")
|
||||||
|
#Try to initialise main process; only fault here should be if the configured listening port is unavailable to us
|
||||||
try:
|
try:
|
||||||
PJControl(PJStates.init)
|
state.init()
|
||||||
|
assert (state.status==(State.States.init & State.States.done))
|
||||||
except:
|
except:
|
||||||
Log(2, "pj-init", "Unable to initialise pjsip library; please check media path and SIP listening port are correct", error=True)
|
state.Log(2, "pj-init", "Unable to initialise pjsip library; please check media path and SIP listening port are correct", error=True)
|
||||||
raise Exception("Unable to initialise pjsip library; please check media path and SIP listening port are correct")
|
raise Exception("Unable to initialise pjsip library; please check media path and SIP listening port are correct")
|
||||||
Log(1, "init-complete", "trashtalker listening on uri %s and serving %s media items from %s" % (state.uri, len(state.playlist), state.source))
|
state.Log(1, "init-complete", "trashtalker listening on uri %s and serving %s media items from %s" % (state.uri, len(state.playlist), state.source))
|
||||||
|
#Enter main loop
|
||||||
try:
|
try:
|
||||||
WaitLoop()
|
state.run()
|
||||||
except pj.Error as e:
|
except pj.Error as e:
|
||||||
Log(2, "pjsip-error", "trashtalker encountered pjsip exception %s" % str(e), error=True)
|
state.Log(2, "pjsip-error", "trashtalker encountered pjsip exception %s" % str(e), error=True)
|
||||||
state.running=False
|
state.stop()
|
||||||
pass
|
pass
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
state.running=False
|
state.stop()
|
||||||
pass
|
pass
|
||||||
Log(1, "deinit", "main loop exited, shutting down")
|
state.Log(1, "deinit", "main loop exited, shutting down")
|
||||||
PJControl(PJStates.deinit)
|
state.deinit()
|
||||||
Log(1, "deinit-complete", "trashtalker has shut down")
|
state.Log(1, "deinit", "trashtalker has shut down")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue