mixer.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. #!/usr/bin/env python3
  2. """
  3. Questo script tiene d'occhio una specifica ConfBridge.
  4. SE c'e' almeno un partecipante, allora cerca di far entrare lo speaker (ovvero
  5. quello che noi chiamiamo "mixer", e che è semplicemente un'estensione PJSIP)
  6. dentro la confbridge
  7. Nella configurazione di asterisk si deve porre attenzione ad un dettaglio: lo
  8. speaker si deve trovare in un contesto diverso da tutti gli altri telefoni, e in
  9. particolare da quello in cui si trovano gli utenti remoti che entrano nella
  10. confbridge in questione. In questo modo si puo' facilmente distinguere lo
  11. speaker.
  12. """
  13. import logging
  14. from argparse import ArgumentParser
  15. from panoramisk.actions import Action
  16. import asterisk_misc_common
  17. CONFERENCE = "400"
  18. APP = None
  19. log = logging.getLogger("transferback")
  20. async def on_tutto(manager, msg):
  21. for prefix in ["Var", "RTC"]:
  22. if msg.event.startswith(prefix):
  23. return
  24. log.info("... Event %s", msg.event)
  25. async def refresh(manager, *args):
  26. ret = await manager.send_action(
  27. Action({"Action": "ConfbridgeList", "Conference": CONFERENCE}), as_list=False
  28. )
  29. if ret.response == "Error":
  30. await APP.on_users(None, ret)
  31. class ConfbridgeInviter:
  32. def __init__(self, manager):
  33. self.manager = manager
  34. self.users = []
  35. self.tmp_users = []
  36. self.log = logging.getLogger(self.__class__.__name__)
  37. self.manager.register_event("EndpointDetail", self.on_speaker_endpoint_info)
  38. self.on_speaker_endpoint_info_active = False
  39. def get_context(self):
  40. return "from-mixer"
  41. def get_extension(self):
  42. return "9400"
  43. def get_speaker_channel(self):
  44. return "PJSIP/400"
  45. def get_conference(self):
  46. return CONFERENCE
  47. def is_speaker(self, msg):
  48. """
  49. questo metodo capisce se un utente e' lo speaker.
  50. Si sarebbe potuto fare anche in altri modi, ad esempio guardando il
  51. channel, o la Exten, o utilizzando admin/marked in maniera opportuna.
  52. """
  53. return msg.context == self.get_context()
  54. async def run(self):
  55. self.log.info("runno")
  56. self.manager.register_event("ConfbridgeList", self.on_user)
  57. self.manager.register_event("ConfbridgeListComplete", self.on_users)
  58. async def on_user(self, _, msg):
  59. if msg.conference != self.get_conference():
  60. return
  61. self.tmp_users.append(msg)
  62. async def on_users(self, _, _msg):
  63. self.users = self.tmp_users
  64. self.tmp_users = []
  65. someone_inside = len(self.users) > 0
  66. speaker_inside = any(True for u in self.users if self.is_speaker(u))
  67. for u in self.users:
  68. self.log.debug(" - %s %s", u.context, u.channel)
  69. self.log.debug(
  70. "%d users - speaker_inside=%s", len(self.users), str(speaker_inside)
  71. )
  72. if not speaker_inside:
  73. if someone_inside:
  74. await self.maybe_invite_speaker()
  75. self.users = []
  76. async def maybe_invite_speaker(self):
  77. self.log.debug("Maybe?")
  78. self.on_speaker_endpoint_info_active = True
  79. ret = await self.manager.send_action(
  80. Action(
  81. {
  82. "Action": "PJSIPShowEndpoint",
  83. "Endpoint": self.get_speaker_channel().split("/")[-1],
  84. }
  85. ),
  86. as_list=False,
  87. )
  88. self.on_speaker_endpoint_info_active = ret.actionid
  89. async def on_speaker_endpoint_info(self, _, msg):
  90. self.log.debug("Vediamo")
  91. active = self.on_speaker_endpoint_info_active
  92. if active is False or (type(active) is str and active != msg.actionid):
  93. return
  94. self.on_speaker_endpoint_info_active = False
  95. self.log.debug(" Speaker dev = %s", msg.devicestate)
  96. if msg.devicestate == "Not in use":
  97. self.invite_speaker()
  98. def invite_speaker(self):
  99. cmd = "channel originate {chan} extension {ext}@{ctx}".format(
  100. chan=self.get_speaker_channel(),
  101. ext=self.get_extension(),
  102. ctx=self.get_context(),
  103. )
  104. self.log.debug("CMD = %s", cmd)
  105. self.manager.send_command(cmd)
  106. async def kick_speaker(self):
  107. return
  108. cmd = "confbridge kick {conf} all".format(
  109. conf=self.get_conference(),
  110. )
  111. self.log.debug("CMD = %s", cmd)
  112. ret = await self.manager.send_command(cmd)
  113. return ret
  114. async def run_app(Cls, manager):
  115. global APP
  116. APP = Cls(manager)
  117. await APP.run()
  118. async def init(manager):
  119. manager.register_event("*", on_tutto)
  120. for event in [
  121. "FullyBooted",
  122. "ConfbridgeStart",
  123. "ConfbridgeJoin",
  124. "ConfbridgeLeave",
  125. "ConfbridgeEnd",
  126. "TestEvent",
  127. "Hangup",
  128. "Newstate", # XXX: solo se lo stato che cambia e' quello dello speaker
  129. ]:
  130. manager.register_event(event, refresh)
  131. pass
  132. await run_app(ConfbridgeInviter, manager)
  133. async def on_shutdown(m):
  134. pass
  135. def main():
  136. p = ArgumentParser()
  137. asterisk_misc_common.add_arguments(p)
  138. args = p.parse_args()
  139. asterisk_misc_common.do_common_options(args)
  140. manager = asterisk_misc_common.get_manager(args)
  141. asterisk_misc_common.run_manager(
  142. manager, args, on_startup=init, on_shutdown=on_shutdown
  143. )
  144. if __name__ == "__main__":
  145. main()