Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 85 additions & 2 deletions pymobiledevice3/cli/developer/core_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,19 @@ async def core_device_display_start_audio_stream(
async def core_device_display_serve_web(
service_provider: RSDServiceProviderDep,
display_id: Annotated[int, typer.Option("--display-id")] = 1,
bind: Annotated[str, typer.Option("--bind", help="Host to bind the webserver on")] = "127.0.0.1",
bind: Annotated[
str,
typer.Option(
"--bind",
help=(
"Host to bind the webserver on. Defaults to ``0.0.0.0`` so the "
"viewer is reachable from any device on the LAN. The /touch / "
"/button / /key endpoints have no auth, so anyone reaching this "
"port can both watch and control the iPhone -- pass ``127.0.0.1`` "
"if that's not what you want."
),
),
] = "0.0.0.0",
http_port: Annotated[int, typer.Option("--http-port", help="Port for the webserver")] = 8080,
no_audio: Annotated[
bool,
Expand All @@ -845,6 +857,45 @@ async def core_device_display_serve_web(
help="Don't auto-enable sound in the viewer (user can still click Enable Sound).",
),
] = False,
ltrp: Annotated[
bool,
typer.Option(
"--ltrp",
help=(
"Opt back into LTRP (long-term reference pictures). LTRP is OFF "
"by default because on-device probing showed the device honours "
"the protobuf-level switch (`IsltrpEnabled: false` in the "
"answer) and LTRP-off eliminates the mid-stream tearing pattern "
"under UDP loss. Apple's captured Xcode offer used LTRP-on; "
"this flag restores that for regression testing."
),
),
] = False,
rtcp_fb: Annotated[
bool,
typer.Option(
"--rtcp-fb",
help=(
"Negotiate `allowRTCPFB=True` in the mediaBlob. No observable "
"effect in streamConfig but may influence internal encoder "
"behaviour."
),
),
] = False,
https: Annotated[
bool,
typer.Option(
"--https",
help=(
"Serve over HTTPS using an ephemeral self-signed certificate. "
"Required for WebCodecs when accessing the viewer from a non-"
"loopback origin: the browser's secure-context policy refuses "
"WebCodecs over plain http:// from any LAN IP. The browser will "
"warn on first visit -- accept the cert and the viewer works "
"normally afterwards."
),
),
] = False,
) -> None:
"""Serve the device's screen via HTTP — view in any modern browser.

Expand All @@ -855,13 +906,18 @@ async def core_device_display_serve_web(

Open ``http://<bind>:<http_port>/`` in Safari or Chrome (macOS Chrome needs
HEVC support — recent versions enable it by default if the OS supports it).
Pass ``--https`` when connecting from another LAN host (browsers gate
WebCodecs on a secure context; only loopback origins bypass that).
"""
server = ScreenStreamServer(
service_provider,
bind=bind,
http_port=http_port,
display_id=display_id,
audio_default_on=not no_audio,
allow_rtcp_fb=rtcp_fb,
ltrp_enabled=ltrp,
https=https,
)
await server.serve()

Expand All @@ -871,7 +927,18 @@ async def core_device_display_serve_web(
async def core_device_display_serve_vnc(
service_provider: RSDServiceProviderDep,
display_id: Annotated[int, typer.Option("--display-id")] = 1,
bind: Annotated[str, typer.Option("--bind", help="Host to bind the VNC listener on")] = "127.0.0.1",
bind: Annotated[
str,
typer.Option(
"--bind",
help=(
"Host to bind the VNC listener on. Defaults to ``0.0.0.0`` so "
"any device on the LAN can connect. The VNC server has no "
"password, so anyone reaching this port can watch AND control "
"the iPhone -- pass ``127.0.0.1`` if that's not acceptable."
),
),
] = "0.0.0.0",
port: Annotated[int, typer.Option("--port", help="TCP port for the VNC listener")] = 5901,
audio: Annotated[
bool,
Expand All @@ -891,6 +958,20 @@ async def core_device_display_serve_vnc(
),
),
] = "auto",
ltrp: Annotated[
bool,
typer.Option(
"--ltrp",
help="Opt back into LTRP (off by default; see serve-web for context).",
),
] = False,
rtcp_fb: Annotated[
bool,
typer.Option(
"--rtcp-fb",
help="Negotiate allowRTCPFB=True in the mediaBlob (experimental).",
),
] = False,
) -> None:
"""Serve the device's screen as a VNC (RFB 3.8) server.

Expand All @@ -917,6 +998,8 @@ async def core_device_display_serve_vnc(
display_id=display_id,
audio=audio,
decoder=decoder,
allow_rtcp_fb=rtcp_fb,
ltrp_enabled=ltrp,
)
await server.serve()

Expand Down
24 changes: 23 additions & 1 deletion pymobiledevice3/remote/core_device/display_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ async def start_video_stream(
display_id: int = 1,
timeout: int = 20,
client_session_id: Optional[uuid.UUID] = None,
*,
allow_rtcp_fb: bool = False,
ltrp_enabled: bool = False,
fec_enabled: bool = True,
tiles_per_frame: int = 1,
) -> dict:
"""Start an RTP video stream of one of the device's displays.

Expand All @@ -67,14 +72,31 @@ async def start_video_stream(
:param timeout: Negotiation timeout in seconds.
:param client_session_id: Stable UUID identifying this session. A fresh UUID
is generated when omitted.
:param allow_rtcp_fb: Set the protobuf-level ``allowRTCPFB`` flag. Default
``False``; shows no observable effect in the device's
``streamConfig`` answer but kept as an opt-in knob in
case it changes internal encoder behaviour.
:param ltrp_enabled: Set the protobuf-level ``ltrpEnabled`` flag. Default
``False`` -- the device honours the request (confirmed
by ``IsltrpEnabled: false`` in the answer's
streamConfig), and LTRP-off eliminates mid-stream
tearing under UDP loss. Apple's captured Xcode offer
used ``True``; opt back in if you suspect a regression.
:return: Response dict with ``connection`` (carries ``sender`` port + full
``streamConfig``) and ``negotiatorAnswer``.
"""
if client_session_id is None:
client_session_id = uuid.uuid4()
call_id = new_call_id()
session_id = random.randint(0, 0xFFFFFFFF)
negotiator_offer = build_negotiator_offer_video(call_id=call_id, session_id=session_id)
negotiator_offer = build_negotiator_offer_video(
call_id=call_id,
session_id=session_id,
allow_rtcp_fb=allow_rtcp_fb,
ltrp_enabled=ltrp_enabled,
fec_enabled=fec_enabled,
tiles_per_frame=tiles_per_frame,
)
request = {
"clientSupportedFeatures": XpcUInt64Type(_CLIENT_SUPPORTED_FEATURES),
"direction": "output",
Expand Down
Loading
Loading