Skip to content

Commit

Permalink
Merge pull request #448 from xsdg/codepath
Browse files Browse the repository at this point in the history
Starts to adjust codepaths to simplify how mkchromecast operates, and to more clearly distinguish between distinct operating modes.
  • Loading branch information
xsdg authored Jan 6, 2024
2 parents 5adef3d + d18f8a6 commit 90c125f
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 109 deletions.
88 changes: 40 additions & 48 deletions bin/mkchromecast
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ from mkchromecast.pulseaudio import create_sink, get_sink_list, remove_sink
from mkchromecast.utils import terminate, checkmktmp, writePidFile
from mkchromecast.messages import print_available_devices


def maybe_execute_single_action(mkcc: mkchromecast.Mkchromecast):
"""Potentially executes a one-off action, followed by exiting."""

Expand Down Expand Up @@ -56,6 +57,8 @@ class CastProcess(object):
checkmktmp()
writePidFile()

atexit.register(self.terminate_app)

self.check_connection()
if self.mkcc.tray is False and self.mkcc.videoarg is False:
if self.mkcc.platform == 'Linux':
Expand All @@ -81,14 +84,14 @@ class CastProcess(object):
self.cc.initialize_cast()
self.get_devices(self.mkcc.select_device)
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

elif self.mkcc.youtube_url is None and self.mkcc.source_url is not None:
self.start_backend(self.mkcc.backend)
self.cc.initialize_cast()
self.get_devices(self.mkcc.select_device)
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

# When casting youtube url, we do it through the audio module
elif self.mkcc.youtube_url is not None and self.mkcc.videoarg is False:
Expand All @@ -97,7 +100,7 @@ class CastProcess(object):
self.cc.initialize_cast()
self.get_devices(self.mkcc.select_device)
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

def audio_macOS(self):
"""This method manages all related to casting audio in macOS"""
Expand All @@ -111,21 +114,21 @@ class CastProcess(object):
outputdev()
print(colors.success('[Done]'))
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

elif self.mkcc.youtube_url is None and self.mkcc.source_url is not None:
self.start_backend(self.mkcc.backend)
self.cc.initialize_cast()
self.get_devices(self.mkcc.select_device)
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

print('Switching to BlackHole...')
inputdev()
outputdev()
print(colors.success('[Done]'))
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

# When casting youtube url, we do it through the audio module
elif self.mkcc.youtube_url is not None and self.mkcc.videoarg is False:
Expand All @@ -134,7 +137,7 @@ class CastProcess(object):
self.cc.initialize_cast()
self.get_devices(self.mkcc.select_device)
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

def cast_video(self):
"""This method launches video casting"""
Expand All @@ -151,7 +154,7 @@ class CastProcess(object):
self.cc.initialize_cast()
self.get_devices(self.mkcc.select_device)
self.cc.play_cast()
self.show_control(self.mkcc.control)
self.block_until_exit()

def get_devices(self, select_device: bool, write_to_pickle: bool = True):
"""Get chromecast name, and let user select one from a list if
Expand Down Expand Up @@ -194,15 +197,18 @@ class CastProcess(object):

def terminate_app(self):
"""Terminate the app (kill app)"""
if self.mkcc.debug:
print(f'terminate_app running in pid {os.getpid()}')

self.cc.stop_cast()
if self.mkcc.platform == 'Darwin':
inputint()
outputint()
elif self.mkcc.platform == 'Linux':
remove_sink()
terminate()
terminate() # Does not return.

def controls_msg(self):
def print_controls_msg(self):
"""Messages shown when controls is True"""
print('')
print(colors.important('Controls:'))
Expand All @@ -218,78 +224,64 @@ class CastProcess(object):
print(colors.options('Quit the Application:')+' q or Ctrl-C')
print('')

def show_control(self, control):
def block_until_exit(self) -> None:
"""Method to show controls"""
if control is True:
from mkchromecast.getch import getch
try:
if self.mkcc.control:
from mkchromecast.getch import getch

self.controls_msg()
self.print_controls_msg()

# We capture keys
try:
while(True):
key = getch()
if(key == 'u'):
self.cc.volume_up()
if self.mkcc.backend == 'ffmpeg':
if self.mkcc.debug is True:
self.controls_msg()
self.print_controls_msg()
elif(key == 'd'):
self.cc.volume_down()
if self.mkcc.backend == 'ffmpeg':
if self.mkcc.debug is True:
self.controls_msg()
self.print_controls_msg()
elif (key == 'a'):
print(self.cc.available_devices)
self.get_devices(self.mkcc.select_device,
write_to_pickle=False)
self.cc.play_cast()
# TODO(xsdg): Why is this recursing?
self.show_control(control)
elif(key == 'p'):
if self.mkcc.videoarg is True:
print('Pausing Casting Process...')
action = 'pause'
self.backend_handler(action, self.mkcc.backend)
if self.mkcc.backend == 'ffmpeg':
if self.mkcc.debug is True:
self.controls_msg()
else:
pass
self.print_controls_msg()
elif(key == 'r'):
if self.mkcc.videoarg is True:
print('Resuming Casting Process...')
action = 'resume'
self.backend_handler(action, self.mkcc.backend)
if self.mkcc.backend == 'ffmpeg':
if self.mkcc.debug is True:
self.controls_msg()
else:
pass
elif(key == 'q'):
print(colors.error('Quitting application...'))
self.terminate_app()
elif(key == '\x03'):
self.print_controls_msg()
elif(key in {'q', '\x03'}):
# "q" or ^D
raise KeyboardInterrupt
# TODO(xsdg): Move atexit to run() method.
atexit.register(self.terminate_app())
except KeyboardInterrupt:
self.terminate_app()

else:
if self.mkcc.platform == 'Linux' and self.mkcc.adevice is None:
print(colors.warning('Remember to open pavucontrol and select '
'the mkchromecast sink.'))
print('')
print(colors.error('Ctrl-C to kill the Application at any Time'))
print('')
# TODO(xsdg): atexit is incorrect here. The signal handlers should
# directly call terminate_app.
signal.signal(signal.SIGINT,
lambda *_: atexit.register(self.terminate_app()))
signal.signal(signal.SIGTERM,
lambda *_: atexit.register(self.terminate_app()))
signal.pause()
else:
if self.mkcc.platform == 'Linux' and self.mkcc.adevice is None:
print(colors.warning('Remember to open pavucontrol and select '
'the mkchromecast sink.'))
print('')
print(colors.error('Ctrl-C to kill the Application at any Time'))
print('')
signal.pause()

except KeyboardInterrupt:
print()
print(colors.error('Quitting application...'))
self.terminate_app()

def backend_handler(self, action, backend):
"""Methods to handle pause and resume state of backends"""
Expand Down
43 changes: 24 additions & 19 deletions mkchromecast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ class Mkchromecast:

def __init__(self, args = None):
# TODO(xsdg): Require arg parsing to be done outside of this class.
first_parse: bool = False
if not args:
if not self._parsed_args:
self._parsed_args = _arg_parsing.Parser.parse_args()
args = self._parsed_args
Mkchromecast._parsed_args = _arg_parsing.Parser.parse_args()
first_parse = True
args = Mkchromecast._parsed_args

self.args = args
self.debug: bool = args.debug
Expand Down Expand Up @@ -202,23 +204,26 @@ def __init__(self, args = None):


# Diagnostic messages
self._debug(f"ALSA device name: {self.adevice}")
self._debug(f"Google Cast name: {self.device_name}")
self._debug(f"backend: {self.backend}")

# TODO(xsdg): These were just printed warnings in the original, but
# should they be errors?
if self.mtype and not self.videoarg:
print(colors.warning(
"The media type argument is only supported for video."))

if self.loop and self.videoarg:
print(colors.warning(
"The loop and video arguments aren't compatible."))

if self.command and not self.videoarg:
print(colors.warning(
"The --command option only works for video."))
if first_parse:
self._debug(f"Parsed args in process {os.getpid()}")

self._debug(f"ALSA device name: {self.adevice}")
self._debug(f"Google Cast name: {self.device_name}")
self._debug(f"backend: {self.backend}")

# TODO(xsdg): These were just printed warnings in the original, but
# should they be errors?
if self.mtype and not self.videoarg:
print(colors.warning(
"The media type argument is only supported for video."))

if self.loop and self.videoarg:
print(colors.warning(
"The loop and video arguments aren't compatible."))

if self.command and not self.videoarg:
print(colors.warning(
"The --command option only works for video."))

def _validate_input_file(self) -> None:
if not self.input_file:
Expand Down
24 changes: 16 additions & 8 deletions mkchromecast/_arg_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ def raise_arg_type_error():
formatter_class=argparse.RawTextHelpFormatter,
)

# All of the "do this action" arguments.
_ActionGroupContainer = Parser.add_argument_group(
title='Actions',
description=('Optional actions that mkchromecast can perform. These are '
'mutually-exclusive.'),
)
_ActionGroup = _ActionGroupContainer.add_mutually_exclusive_group()

Parser.add_argument(
"--alsa-device",
type=str,
Expand Down Expand Up @@ -176,7 +184,7 @@ def raise_arg_type_error():
""",
)

Parser.add_argument(
_ActionGroup.add_argument(
"-d",
"--discover",
action="store_true",
Expand Down Expand Up @@ -241,7 +249,7 @@ def raise_arg_type_error():
""",
)

Parser.add_argument(
_ActionGroup.add_argument(
"-i",
"--input-file",
type=str,
Expand Down Expand Up @@ -314,7 +322,7 @@ def raise_arg_type_error():
)


Parser.add_argument(
_ActionGroup.add_argument(
"-r",
"--reset",
action="store_true",
Expand Down Expand Up @@ -391,7 +399,7 @@ def raise_arg_type_error():
""",
)

Parser.add_argument(
_ActionGroup.add_argument(
"--screencast",
action="store_true",
default=False,
Expand Down Expand Up @@ -433,7 +441,7 @@ def raise_arg_type_error():
""",
)

Parser.add_argument(
_ActionGroup.add_argument(
"--source-url",
type=str,
default=None,
Expand Down Expand Up @@ -473,7 +481,7 @@ def raise_arg_type_error():
""",
)

Parser.add_argument(
_ActionGroup.add_argument(
"-t",
"--tray",
action="store_true",
Expand All @@ -498,7 +506,7 @@ def raise_arg_type_error():
help="Do not use. Argument dropped.",
)

Parser.add_argument(
_ActionGroup.add_argument(
"-v",
"--version",
action="store_true",
Expand Down Expand Up @@ -542,7 +550,7 @@ def raise_arg_type_error():
help="Do not use. Renamed to --control.",
)

Parser.add_argument(
_ActionGroup.add_argument(
"-y",
"--youtube",
type=str,
Expand Down
21 changes: 13 additions & 8 deletions mkchromecast/stream_infra.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,30 +262,35 @@ def start(self):

@staticmethod
def _monitor_loop(platform: str):
f = open("/tmp/mkchromecast.pid", "rb")
pidnumber = int(pickle.load(f))
print(colors.options("PID of main process:") + " " + str(pidnumber))
with open("/tmp/mkchromecast.pid", "rb") as pid_file:
main_pid = int(pickle.load(pid_file))
print(colors.options("PID of main process:") + f" {main_pid}")

localpid = os.getpid()
print(colors.options("PID of streaming process:") + " " + str(localpid))
local_pid = os.getpid()
print(colors.options("PID of streaming process:") + f" {os.getpid()}")

while psutil.pid_exists(localpid) is True:
while psutil.pid_exists(local_pid):
try:
time.sleep(0.5)
# With this I ensure that if the main app fails, everything
# will get back to normal
if psutil.pid_exists(pidnumber) is False:
if not psutil.pid_exists(main_pid):
if platform == "Darwin":
inputint()
outputint()
else:
from mkchromecast.pulseaudio import remove_sink

remove_sink()
parent = psutil.Process(localpid)
parent = psutil.Process(local_pid)
# TODO(xsdg): This is unlikely to finish, given that this
# code itself is running in one of the child processes. We
# should instead signal the parent to terminate, and have it
# handle child cleanup on its own.
for child in parent.children(recursive=True):
child.kill()
parent.kill()

except KeyboardInterrupt:
print("Ctrl-c was requested")
sys.exit(0)
Expand Down
Loading

0 comments on commit 90c125f

Please sign in to comment.