Repository: streamlink/streamlink

Issue:
cli: --player="" --player-passthrough=hls causes AttributeError: 'NoneType' object has no attribute 'name'
### Checklist

- [X] This is a bug report and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)
- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
- [X] [I have checked the list of open and recently closed bug reports](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22bug%22)
- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)

### Collaboration

- [X] [I will provide feedback should a pull request be opened with a fix for the problem](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#pull-request-feedback)

### Streamlink version

[cli][info] Your Streamlink version (6.10.0) is up to date!

### Description

Whenever i use --player-passthrough=hls it gives nonetype error while http work fine but does not provide all the information to vlc player such as total time and seekablity.
I am using streamlink on termux android and using passthrough with twitch as well as with m3u8 links. 

### Debug log

```text
"https://worker.vidlink.pro/catflix?url=https://s.ezwatch.xyz/stream_6602d21d8a416/uwu.m3u8" best --player-passthrough=hls --loglevel=debug
[session][debug] Loading plugin: hls
[cli][debug] OS:         Linux-4.19.191+-armv8l-with-libc
[cli][debug] Python:     3.11.10
[cli][debug] OpenSSL:    OpenSSL 3.3.2 3 Sep 2024
[cli][debug] Streamlink: 6.10.0
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.8.30
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.20.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.26.2
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  typing-extensions: 4.12.2
[cli][debug]  urllib3: 2.2.3
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=https://worker.vidlink.pro/catflix?url=https://s.ezwatch.xyz/stream_6602d21d8a416/uwu.m3u8
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --player-continuous-http=True
[cli][debug]  --player-external-http=True
[cli][debug]  --player-external-http-interface=127.0.0.1
[cli][debug]  --player-external-http-port=4567
[cli][debug]  --player-passthrough=['hls']
[cli][info] Found matching plugin hls for URL https://worker.vidlink.pro/catflix?url=https://s.ezwatch.xyz/stream_6602d21d8a416/uwu.m3u8
[plugins.hls][debug] URL=https://worker.vidlink.pro/catflix?url=https://s.ezwatch.xyz/stream_6602d21d8a416/uwu.m3u8; params={}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: live (worst, best)
[cli][info] Opening stream: live (hls)
Traceback (most recent call last):
  File "/data/data/com.termux/files/usr/bin/streamlink", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/streamlink_cli/main.py", line 990, in main
    exit_code = run(parser)
                ^^^^^^^^^^^
  File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/streamlink_cli/main.py", line 957, in run
    handle_url()
  File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/streamlink_cli/main.py", line 564, in handle_url
    handle_stream(plugin, streams, stream_name)
  File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/streamlink_cli/main.py", line 422, in handle_stream
    success = output_stream_passthrough(stream, formatter)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/streamlink_cli/main.py", line 279, in output_stream_passthrough
    output = PlayerOutput(
             ^^^^^^^^^^^^^
  File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/streamlink_cli/output/player.py", line 189, in __init__
    self.playerargs = self.playerargsfactory(
                      ^^^^^^^^^^^^^^^^^^^^^^^
  File "/data/data/com.termux/files/usr/lib/python3.11/site-packages/streamlink_cli/output/player.py", line 212, in playerargsfactory
    executable = path.name
                 ^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'name'
```


Discussion:

Patch:
diff --git a/src/streamlink_cli/main.py b/src/streamlink_cli/main.py
index 44ab0964..d3dfeabc 100644
--- a/src/streamlink_cli/main.py
+++ b/src/streamlink_cli/main.py
@@ -292,6 +292,12 @@ def output_stream_passthrough(stream, formatter: Formatter):
     except TypeError:
         raise StreamlinkCLIError("The stream specified cannot be translated to a URL") from None
 
+    if not args.player:
+        raise StreamlinkCLIError(
+            "The default player (VLC) does not seem to be installed."
+            + " You must specify the path to a player executable with --player.",
+        )
+
     output = PlayerOutput(
         path=args.player,
         args=args.player_args,


Test patch:
diff --git a/tests/cli/main/test_output_stream_passthrough.py b/tests/cli/main/test_output_stream_passthrough.py
new file mode 100644
index 00000000..9c7a03be
--- /dev/null
+++ b/tests/cli/main/test_output_stream_passthrough.py
@@ -0,0 +1,31 @@
+import re
+from pathlib import Path
+
+import pytest
+
+import streamlink_cli.main
+import tests
+from streamlink.session import Streamlink
+
+
+@pytest.fixture(autouse=True)
+def session(session: Streamlink):
+    session.plugins.load_path(Path(tests.__path__[0]) / "plugin")
+
+    return session
+
+
+@pytest.mark.parametrize(
+    "argv",
+    [pytest.param(["--player-passthrough=hls", "http://test.se", "hls"], id="no-player")],
+    indirect=["argv"],
+)
+def test_no_player(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], argv: list):
+    monkeypatch.setattr("streamlink_cli.argparser.find_default_player", lambda *_, **__: None)
+
+    with pytest.raises(SystemExit) as exc_info:
+        streamlink_cli.main.main()
+    assert exc_info.value.code == 1
+
+    out, _err = capsys.readouterr()
+    assert re.search(r"error: The default player \(\w+\) does not seem to be installed\.", out)