Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ def _parse_response(

return response_500

if response.status_code == 503:
response_503 = ApiError.from_dict(response.json())

return response_503

if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ class RegisterInputResponse:
endpoint_route (None | str | Unset):
video_duration_ms (int | None | Unset):
audio_duration_ms (int | None | Unset):
port (int | None | Unset):
publish_url (None | str | Unset):
"""

bearer_token: None | str | Unset = UNSET
endpoint_route: None | str | Unset = UNSET
video_duration_ms: int | None | Unset = UNSET
audio_duration_ms: int | None | Unset = UNSET
port: int | None | Unset = UNSET
publish_url: None | str | Unset = UNSET

def to_dict(self) -> dict[str, Any]:
bearer_token: None | str | Unset
Expand All @@ -52,11 +52,11 @@ def to_dict(self) -> dict[str, Any]:
else:
audio_duration_ms = self.audio_duration_ms

port: int | None | Unset
if isinstance(self.port, Unset):
port = UNSET
publish_url: None | str | Unset
if isinstance(self.publish_url, Unset):
publish_url = UNSET
else:
port = self.port
publish_url = self.publish_url

field_dict: dict[str, Any] = {}

Expand All @@ -69,8 +69,8 @@ def to_dict(self) -> dict[str, Any]:
field_dict["video_duration_ms"] = video_duration_ms
if audio_duration_ms is not UNSET:
field_dict["audio_duration_ms"] = audio_duration_ms
if port is not UNSET:
field_dict["port"] = port
if publish_url is not UNSET:
field_dict["publish_url"] = publish_url

return field_dict

Expand Down Expand Up @@ -114,21 +114,21 @@ def _parse_audio_duration_ms(data: object) -> int | None | Unset:

audio_duration_ms = _parse_audio_duration_ms(d.pop("audio_duration_ms", UNSET))

def _parse_port(data: object) -> int | None | Unset:
def _parse_publish_url(data: object) -> None | str | Unset:
if data is None:
return data
if isinstance(data, Unset):
return data
return cast(int | None | Unset, data)
return cast(None | str | Unset, data)

port = _parse_port(d.pop("port", UNSET))
publish_url = _parse_publish_url(d.pop("publish_url", UNSET))

register_input_response = cls(
bearer_token=bearer_token,
endpoint_route=endpoint_route,
video_duration_ms=video_duration_ms,
audio_duration_ms=audio_duration_ms,
port=port,
publish_url=publish_url,
)

return register_input_response
23 changes: 19 additions & 4 deletions fishjam/api/_composition_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,23 +433,38 @@ def register_mp4_input(

def register_rtmp_input(
self, composition_id: str, input_id: str, *, stream_key: str
) -> None:
) -> str:
"""Register an input that an RTMP publisher pushes media into.

The stream key identifies the input; the address to publish to belongs to the
composition, not to this call.
The stream key identifies the input and is carried in the returned address.

Args:
composition_id: ID of the composition.
input_id: ID to register the input under.
stream_key: Key the publisher identifies the input with.

Returns:
The address to publish the RTMP stream to.

Raises:
InternalServerError: When the server reports no address, leaving the input
impossible to publish to.
"""
self.register_input(
response = self.register_input(
composition_id,
input_id,
RtmpInput(type_=RtmpInputType.RTMP_SERVER, stream_key=stream_key),
)

publish_url = _or_none(response.publish_url)
if not publish_url:
raise InternalServerError(
f'Registering RTMP input "{input_id}" returned no publishing address, '
"so it cannot be published to"
)

return publish_url

def unregister_input(
self,
composition_id: str,
Expand Down
16 changes: 15 additions & 1 deletion tests/test_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
IMAGE_ID = "logo"
LOCAL_URL = "http://localhost:8000"
FONT_PATH = Path(__file__).parent / "fixtures" / "font.ttf"
RTMP_PUBLISH_URL = "rtmps://rtmp.example.com:443/key"


def client(composition_url: str | None = None) -> CompositionClient:
Expand Down Expand Up @@ -211,11 +212,24 @@ def test_sends_the_mp4_discriminant(self):
}

def test_sends_the_rtmp_discriminant(self):
with mock_response() as requests:
with mock_response({"publish_url": RTMP_PUBLISH_URL}) as requests:
client().register_rtmp_input(COMPOSITION_ID, INPUT_ID, stream_key="key")

assert sent_json(requests) == {"type": "rtmp_server", "stream_key": "key"}

def test_returns_the_rtmp_publishing_address_the_server_chose(self):
with mock_response({"publish_url": RTMP_PUBLISH_URL}):
url = client().register_rtmp_input(
COMPOSITION_ID, INPUT_ID, stream_key="key"
)

assert url == RTMP_PUBLISH_URL

def test_raises_when_no_rtmp_publishing_address_is_available(self):
with mock_response():
with pytest.raises(InternalServerError):
client().register_rtmp_input(COMPOSITION_ID, INPUT_ID, stream_key="key")

def test_returns_the_durations_of_an_mp4_input(self):
with mock_response({"video_duration_ms": 1000, "audio_duration_ms": 2000}):
durations = client().register_mp4_input(
Expand Down
Loading