Skip to content

Commit 9417bd8

Browse files
authored
Merge pull request #3 from membraneframework-labs/support-one-media-type
Support one media type
2 parents 4023ad9 + 6740b8f commit 9417bd8

File tree

12 files changed

+112
-25
lines changed

12 files changed

+112
-25
lines changed

lib/ex_hls/client.ex

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ defmodule ExHLS.Client do
3535
media_playlist: nil,
3636
media_base_url: nil,
3737
multivariant_playlist: multivariant_playlist,
38+
root_playlist_string: request_body,
3839
base_url: Path.dirname(url),
3940
video_chunks: [],
4041
demuxing_engine_impl: nil,
4142
demuxing_engine: nil,
43+
media_types: [:audio, :video],
4244
queues: %{audio: Qex.new(), video: Qex.new()},
4345
timestamp_offsets: %{audio: nil, video: nil},
4446
last_timestamps: %{audio: nil, video: nil}
@@ -67,13 +69,9 @@ defmodule ExHLS.Client do
6769
defp ensure_media_playlist_loaded(client), do: client
6870

6971
defp read_media_playlist_without_variant(%{media_playlist: nil} = client) do
70-
media_playlist =
71-
client.base_url
72-
|> Path.join("output.m3u8")
73-
|> Req.get!()
74-
7572
deserialized_media_playlist =
76-
ExM3U8.deserialize_media_playlist!(media_playlist.body, [])
73+
client.root_playlist_string
74+
|> ExM3U8.deserialize_media_playlist!([])
7775

7876
%{
7977
client
@@ -136,12 +134,13 @@ defmodule ExHLS.Client do
136134
end
137135
end
138136

139-
@spec do_read_chunk(client(), :audio | :video) :: {chunk() | :end_of_stream, client()}
137+
@spec do_read_chunk(client(), :audio | :video) ::
138+
{chunk() | :end_of_stream | {:error, atom()}, client()}
140139
defp do_read_chunk(client, media_type) do
141140
client = ensure_media_playlist_loaded(client)
142141

143142
with impl when impl != nil <- client.demuxing_engine_impl,
144-
track_id <- get_track_id!(client, media_type),
143+
{:ok, track_id} <- get_track_id(client, media_type),
145144
{:ok, chunk, demuxing_engine} <- client.demuxing_engine |> impl.pop_chunk(track_id) do
146145
client =
147146
with %{timestamp_offsets: %{^media_type => nil}} <- client do
@@ -152,6 +151,12 @@ defmodule ExHLS.Client do
152151

153152
{chunk, client}
154153
else
154+
# returned from the second match
155+
:error ->
156+
client = %{client | media_types: client.media_types -- [media_type]}
157+
{{:error, :no_track_for_media_type}, client}
158+
159+
# returned from the first or the third match
155160
other ->
156161
case other do
157162
{:error, _reason, demuxing_engine} -> %{client | demuxing_engine: demuxing_engine}
@@ -175,29 +180,35 @@ defmodule ExHLS.Client do
175180
else
176181
_other ->
177182
media_type = media_type_with_lower_ts(client)
178-
{chunk_or_eos, client} = do_read_chunk(client, media_type)
183+
{chunk_eos_or_error, client} = do_read_chunk(client, media_type)
179184

180-
with %ExHLS.Chunk{} <- chunk_or_eos do
185+
with %ExHLS.Chunk{} = chunk <- chunk_eos_or_error do
181186
client
182-
|> update_in([:queues, media_type], &Qex.push(&1, chunk_or_eos))
187+
|> update_in([:queues, media_type], &Qex.push(&1, chunk))
183188
|> get_tracks_info()
184189
else
185190
:end_of_stream ->
186191
{:error, "end of stream reached, but tracks info is not available", client}
192+
193+
{:error, :no_track_for_media_type} when client.media_types != [] ->
194+
client |> get_tracks_info()
195+
196+
{:error, :no_track_for_media_type} when client.media_types == [] ->
197+
{:error, "no supported media types in HLS stream", client}
187198
end
188199
end
189200
end
190201

191202
defp media_type_with_lower_ts(client) do
192203
cond do
193-
client.timestamp_offsets.audio == nil ->
204+
client.timestamp_offsets.audio == nil and :audio in client.media_types ->
194205
:audio
195206

196-
client.timestamp_offsets.video == nil ->
207+
client.timestamp_offsets.video == nil and :video in client.media_types ->
197208
:video
198209

199210
true ->
200-
[:audio, :video]
211+
client.media_types
201212
|> Enum.min_by(fn media_type ->
202213
client.last_timestamps[media_type] - client.timestamp_offsets[media_type]
203214
end)
@@ -260,13 +271,6 @@ defmodule ExHLS.Client do
260271
}
261272
end
262273

263-
defp get_track_id!(client, type) when type in [:audio, :video] do
264-
case get_track_id(client, type) do
265-
{:ok, track_id} -> track_id
266-
:error -> raise "Track ID for #{type} not found in client #{inspect(client, pretty: true)}"
267-
end
268-
end
269-
270274
defp get_track_id(client, type) when type in [:audio, :video] do
271275
impl = client.demuxing_engine_impl
272276

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defmodule ExHLS.Mixfile do
3737

3838
defp deps do
3939
[
40-
{:ex_m3u8, "~> 0.15.2"},
40+
{:ex_m3u8, "~> 0.15.3"},
4141
{:req, "~> 0.5.10"},
4242
{:qex, "~> 0.5.1"},
4343
{:membrane_mp4_plugin, "~> 0.35.3"},

test/client_test.exs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ defmodule Client.Test do
22
use ExUnit.Case, async: true
33

44
alias ExHLS.Client
5-
65
alias Membrane.{AAC, H264, RemoteStream}
76

7+
@fixtures "https://raw.githubusercontent.com/membraneframework-labs/ex_hls/refs/heads/support-one-media-type/test/fixtures/"
8+
@fmp4_url @fixtures <> "fmp4/output.m3u8"
9+
@fmp4_only_video_url @fixtures <> "fmp4_only_video/output.m3u8"
10+
@mpegts_only_video_url @fixtures <> "mpeg_ts_only_video/output_playlist.m3u8"
811
@mpegts_url "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
9-
@fmp4_url "https://raw.githubusercontent.com/membraneframework-labs/ex_hls/refs/heads/plug-demuxing-engine-into-client/fixture/output.m3u8"
12+
1013
describe "if client reads video and audio chunks of the HLS" do
1114
test "(MPEGTS) stream" do
1215
client = Client.new(@mpegts_url)
@@ -46,7 +49,6 @@ defmodule Client.Test do
4649
33, 70, 254, 208, 221, 101, 200, 21, 97, 0>> <> _rest = audio_chunk.payload
4750
end
4851

49-
@tag :a
5052
test "(fMP4) stream" do
5153
client = Client.new(@fmp4_url)
5254

@@ -97,4 +99,51 @@ defmodule Client.Test do
9799
assert second_audio_chunk.payload == <<33, 16, 4, 96, 140, 28>>
98100
end
99101
end
102+
103+
test "(MPEGTS) stream with only video" do
104+
client = Client.new(@mpegts_only_video_url)
105+
106+
assert Client.get_variants(client) == %{}
107+
assert {:ok, tracks_info, client} = Client.get_tracks_info(client)
108+
109+
assert [%Membrane.RemoteStream{content_format: Membrane.H264, type: :bytestream}] =
110+
tracks_info |> Map.values()
111+
112+
{video_chunk, _client} = Client.read_video_chunk(client)
113+
114+
assert %{pts_ms: 1480, dts_ms: 1400} = video_chunk
115+
assert byte_size(video_chunk.payload) == 822
116+
117+
assert <<0, 0, 0, 1, 9, 240, 0, 0, 0, 1, 6, 5, 255, 255, 167, 220, 69, 233, 189, 230, 217, 72,
118+
183, 150, 44, 216, 32, 217, 35, 238, 239, 120, 50, 54, 52, 32, 45, 32, 99, 111, 114,
119+
101, 32, 49, 54, 52, 32, 114>> <> _rest = video_chunk.payload
120+
121+
assert video_chunk.metadata == %{discontinuity: false, is_aligned: false}
122+
end
123+
124+
test "(fMP4) stream with only video" do
125+
client = Client.new(@fmp4_only_video_url)
126+
127+
assert Client.get_variants(client) == %{}
128+
assert {:ok, tracks_info, client} = Client.get_tracks_info(client)
129+
130+
assert [
131+
%H264{
132+
width: 480,
133+
height: 270,
134+
alignment: :au,
135+
nalu_in_metadata?: false,
136+
stream_structure: {:avc1, _binary}
137+
}
138+
] = tracks_info |> Map.values()
139+
140+
{video_chunk, _client} = Client.read_video_chunk(client)
141+
142+
assert %{pts_ms: 0, dts_ms: 0} = video_chunk
143+
assert byte_size(video_chunk.payload) == 823
144+
145+
assert <<0, 0, 0, 2, 9, 240, 0, 0, 2, 171, 6, 5, 255, 255, 167, 220, 69, 233, 189, 230, 217,
146+
72, 183, 150, 44, 216, 32, 217, 35, 238, 239, 120, 50, 54, 52, 32, 45, 32, 99, 111,
147+
114, 101, 32, 49, 54, 52, 32, 114>> <> _rest = video_chunk.payload
148+
end
100149
end
File renamed without changes.
File renamed without changes.
File renamed without changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#EXTM3U
2+
#EXT-X-VERSION:7
3+
#EXT-X-TARGETDURATION:4
4+
#EXT-X-MEDIA-SEQUENCE:0
5+
#EXT-X-PLAYLIST-TYPE:VOD
6+
#EXT-X-MAP:URI="output.m4s",BYTERANGE="848@0"
7+
#EXTINF:3.840625,
8+
#EXT-X-BYTERANGE:93742@848
9+
output.m4s
10+
#EXTINF:1.920313,
11+
#EXT-X-BYTERANGE:219741@94590
12+
output.m4s
13+
#EXTINF:1.920313,
14+
#EXT-X-BYTERANGE:148297@314331
15+
output.m4s
16+
#EXTINF:1.920313,
17+
#EXT-X-BYTERANGE:45690@462628
18+
output.m4s
19+
#EXTINF:0.400065,
20+
#EXT-X-BYTERANGE:34649@508318
21+
output.m4s
22+
#EXT-X-ENDLIST
530 KB
Binary file not shown.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#EXTM3U
2+
#EXT-X-VERSION:3
3+
#EXT-X-TARGETDURATION:6
4+
#EXT-X-MEDIA-SEQUENCE:0
5+
#EXT-X-PLAYLIST-TYPE:VOD
6+
#EXTINF:5.760933,
7+
video_segment_000.ts
8+
#EXTINF:3.840633,
9+
video_segment_001.ts
10+
#EXTINF:0.400111,
11+
video_segment_002.ts
12+
#EXT-X-ENDLIST
330 KB
Binary file not shown.

0 commit comments

Comments
 (0)