Skip to content

Commit e42c09d

Browse files
authored
Add jitter buffer (#158)
1 parent f310ba0 commit e42c09d

File tree

12 files changed

+1275
-29
lines changed

12 files changed

+1275
-29
lines changed

examples/save_to_file/lib/save_to_file/peer_handler.ex

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ defmodule SaveToFile.PeerHandler do
1010
}
1111

1212
alias ExWebRTC.Media.{IVF, Ogg}
13-
alias ExWebRTC.RTP.Depayloader
13+
alias ExWebRTC.RTP.{Depayloader, JitterBuffer}
1414

1515
@behaviour WebSock
1616

17+
@jitter_buffer_latency_ms 100
18+
1719
@video_file "./video.ivf"
1820
@audio_file "./audio.ogg"
1921

@@ -53,8 +55,10 @@ defmodule SaveToFile.PeerHandler do
5355
audio_track_id: nil,
5456
video_writer: nil,
5557
video_depayloader: nil,
58+
video_buffer: nil,
5659
audio_writer: nil,
5760
audio_depayloader: nil,
61+
audio_buffer: nil,
5862
frames_cnt: 0
5963
}
6064

@@ -73,10 +77,22 @@ defmodule SaveToFile.PeerHandler do
7377
handle_webrtc_msg(msg, state)
7478
end
7579

80+
@impl true
81+
def handle_info({:jitter_buffer_timer, kind}, state) do
82+
case kind do
83+
:video -> state.video_buffer
84+
:audio -> state.audio_buffer
85+
end
86+
|> JitterBuffer.handle_timer()
87+
|> handle_jitter_buffer_result(kind, state)
88+
end
89+
7690
@impl true
7791
def terminate(reason, state) do
7892
Logger.warning("WebSocket connection was terminated, reason: #{inspect(reason)}")
7993

94+
state = flush_jitter_buffers(state)
95+
8096
if state.video_writer, do: IVF.Writer.close(state.video_writer)
8197
if state.audio_writer, do: Ogg.Writer.close(state.audio_writer)
8298
end
@@ -142,11 +158,13 @@ defmodule SaveToFile.PeerHandler do
142158
)
143159

144160
{:ok, video_depayloader} = @video_codecs |> hd() |> Depayloader.new()
161+
video_buffer = JitterBuffer.new(latency: @jitter_buffer_latency_ms)
145162

146163
state = %{
147164
state
148165
| video_depayloader: video_depayloader,
149166
video_writer: video_writer,
167+
video_buffer: video_buffer,
150168
video_track_id: id
151169
}
152170

@@ -157,11 +175,13 @@ defmodule SaveToFile.PeerHandler do
157175
# by default uses 1 mono channel and 48k clock rate
158176
{:ok, audio_writer} = Ogg.Writer.open(@audio_file)
159177
{:ok, audio_depayloader} = @audio_codecs |> hd() |> Depayloader.new()
178+
audio_buffer = JitterBuffer.new(latency: @jitter_buffer_latency_ms)
160179

161180
state = %{
162181
state
163182
| audio_depayloader: audio_depayloader,
164183
audio_writer: audio_writer,
184+
audio_buffer: audio_buffer,
165185
audio_track_id: id
166186
}
167187

@@ -175,32 +195,78 @@ defmodule SaveToFile.PeerHandler do
175195
end
176196

177197
defp handle_webrtc_msg({:rtp, id, nil, packet}, %{video_track_id: id} = state) do
198+
state.video_buffer
199+
|> JitterBuffer.place_packet(packet)
200+
|> handle_jitter_buffer_result(:video, state)
201+
end
202+
203+
defp handle_webrtc_msg({:rtp, id, nil, packet}, %{audio_track_id: id} = state) do
204+
state.audio_buffer
205+
|> JitterBuffer.place_packet(packet)
206+
|> handle_jitter_buffer_result(:audio, state)
207+
end
208+
209+
defp handle_webrtc_msg(_msg, state), do: {:ok, state}
210+
211+
defp handle_jitter_buffer_result({packets, timer, buffer}, kind, state) do
178212
state =
179-
case Depayloader.depayload(state.video_depayloader, packet) do
180-
{nil, video_depayloader} ->
181-
%{state | video_depayloader: video_depayloader}
182-
183-
{vp8_frame, video_depayloader} ->
184-
frame = %IVF.Frame{timestamp: state.frames_cnt, data: vp8_frame}
185-
{:ok, video_writer} = IVF.Writer.write_frame(state.video_writer, frame)
186-
187-
%{
188-
state
189-
| video_depayloader: video_depayloader,
190-
video_writer: video_writer,
191-
frames_cnt: state.frames_cnt + 1
192-
}
213+
case kind do
214+
:video -> %{state | video_buffer: buffer}
215+
:audio -> %{state | audio_buffer: buffer}
193216
end
194217

218+
state =
219+
Enum.reduce(packets, state, fn packet, state -> handle_packet(packet, kind, state) end)
220+
221+
unless is_nil(timer), do: Process.send_after(self(), {:jitter_buffer_timer, kind}, timer)
222+
195223
{:ok, state}
196224
end
197225

198-
defp handle_webrtc_msg({:rtp, id, nil, packet}, %{audio_track_id: id} = state) do
226+
defp handle_packet(packet, :video, state) do
227+
case Depayloader.depayload(state.video_depayloader, packet) do
228+
{nil, video_depayloader} ->
229+
%{state | video_depayloader: video_depayloader}
230+
231+
{vp8_frame, video_depayloader} ->
232+
frame = %IVF.Frame{timestamp: state.frames_cnt, data: vp8_frame}
233+
{:ok, video_writer} = IVF.Writer.write_frame(state.video_writer, frame)
234+
235+
%{
236+
state
237+
| video_depayloader: video_depayloader,
238+
video_writer: video_writer,
239+
frames_cnt: state.frames_cnt + 1
240+
}
241+
end
242+
end
243+
244+
defp handle_packet(packet, :audio, state) do
199245
{opus_packet, depayloader} = Depayloader.depayload(state.audio_depayloader, packet)
200246
{:ok, audio_writer} = Ogg.Writer.write_packet(state.audio_writer, opus_packet)
201247

202-
{:ok, %{state | audio_depayloader: depayloader, audio_writer: audio_writer}}
248+
%{state | audio_depayloader: depayloader, audio_writer: audio_writer}
203249
end
204250

205-
defp handle_webrtc_msg(_msg, state), do: {:ok, state}
251+
defp flush_jitter_buffers(state),
252+
do: state |> flush_jitter_buffer(:video) |> flush_jitter_buffer(:audio)
253+
254+
defp flush_jitter_buffer(state, kind) do
255+
buffer =
256+
case kind do
257+
:video -> state.video_buffer
258+
:audio -> state.audio_buffer
259+
end
260+
261+
if is_nil(buffer) do
262+
state
263+
else
264+
{:ok, state} =
265+
buffer
266+
|> JitterBuffer.flush()
267+
|> handle_jitter_buffer_result(kind, state)
268+
269+
state
270+
end
271+
end
206272
end

examples/save_to_file/mix.lock

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
"bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"},
44
"bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"},
55
"bundlex": {:hex, :bundlex, "1.5.3", "35d01e5bc0679510dd9a327936ffb518f63f47175c26a35e708cc29eaec0890b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "debd0eac151b404f6216fc60222761dff049bf26f7d24d066c365317650cd118"},
6-
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
76
"crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"},
87
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
98
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
109
"ex_dtls": {:hex, :ex_dtls, "0.16.0", "3ae38025ccc77f6db573e2e391602fa9bbc02253c137d8d2d59469a66cbe806b", [:mix], [{:bundlex, "~> 1.5.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2a4e30d74c6ddf95cc5b796423293c06a0da295454c3823819808ff031b4b361"},
11-
"ex_ice": {:hex, :ex_ice, "0.8.0", "f9bd181e8fd2f8ac9a808587ee8a47bf667143069d75f6e4892a62156d798aa7", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.1.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "b0476f6b18986f6df48fda4cecb3be5022323572790d1bb49da10b177c936b4e"},
10+
"ex_ice": {:hex, :ex_ice, "0.8.1", "4d5c911766ce92e13323b632a55d9ab821092f13fc2ebf236dc233c8c1f9a64c", [:mix], [{:elixir_uuid, "~> 1.0", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:ex_stun, "~> 0.2.0", [hex: :ex_stun, repo: "hexpm", optional: false]}, {:ex_turn, "~> 0.1.0", [hex: :ex_turn, repo: "hexpm", optional: false]}], "hexpm", "8f10134e2eb7e6aebbf8fba0d5fcec56d8f8db3e94c3dde045feb463979c2dda"},
1211
"ex_libsrtp": {:hex, :ex_libsrtp, "0.7.2", "211bd89c08026943ce71f3e2c0231795b99cee748808ed3ae7b97cd8d2450b6b", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "2e20645d0d739a4ecdcf8d4810a0c198120c8a2f617f2b75b2e2e704d59f492a"},
1312
"ex_rtcp": {:hex, :ex_rtcp, "0.4.0", "f9e515462a9581798ff6413583a25174cfd2101c94a2ebee871cca7639886f0a", [:mix], [], "hexpm", "28956602cf210d692fcdaf3f60ca49681634e1deb28ace41246aee61ee22dc3b"},
1413
"ex_rtp": {:hex, :ex_rtp, "0.4.0", "1f1b5c1440a904706011e3afbb41741f5da309ce251cb986690ce9fd82636658", [:mix], [], "hexpm", "0f72d80d5953a62057270040f0f1ee6f955c08eeae82ac659c038001d7d5a790"},
@@ -22,7 +21,6 @@
2221
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
2322
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
2423
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
25-
"nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"},
2624
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
2725
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
2826
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
@@ -33,6 +31,6 @@
3331
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
3432
"unifex": {:hex, :unifex, "1.2.0", "90d1ec5e6d788350e07e474f7bd8b0ee866d6606beb9ca4e20dbb26328712a84", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "7a8395aabc3ba6cff04bbe5b995de7f899a38eb57f189e49927d6b8b6ccb6883"},
3533
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
36-
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
34+
"websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
3735
"zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"},
3836
}

lib/ex_webrtc/rtp/jitter_buffer.ex

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
defmodule ExWebRTC.RTP.JitterBuffer do
2+
@moduledoc """
3+
Buffers and reorders RTP packets based on `sequence_number`, introducing controlled latency
4+
in order to combat network jitter and improve the QoS.
5+
"""
6+
7+
# Heavily inspired by:
8+
# https://github.com/membraneframework/membrane_rtp_plugin/blob/23f3279540aea7dea3a194fd5a1680c2549aebae/lib/membrane/rtp/jitter_buffer.ex
9+
10+
alias ExWebRTC.RTP.JitterBuffer.PacketStore
11+
alias ExRTP.Packet
12+
13+
@default_latency_ms 200
14+
15+
@typedoc """
16+
Options that can be passed to `new/1`.
17+
18+
* `latency` - latency introduced by the buffer, in milliseconds. `#{@default_latency_ms}` by default.
19+
"""
20+
@type options :: [latency: non_neg_integer()]
21+
22+
@typedoc """
23+
Time (in milliseconds) after which `handle_timeout/1` should be called.
24+
Can be `nil`, in which case no timer needs to be set.
25+
"""
26+
@type timer :: non_neg_integer() | nil
27+
28+
@typedoc """
29+
The 3-element tuple returned by all functions other than `new/1`.
30+
31+
* `packets` - a list with packets flushed from the buffer as a result of the function call. May be empty.
32+
* `timer_duration_ms` - see `t:timer/0`.
33+
* `buffer` - `t:#{inspect(__MODULE__)}.t/0`.
34+
35+
Generally speaking, all results of this type can be handled in the same way.
36+
"""
37+
@type result :: {packets :: [Packet.t()], timer_duration_ms :: timer(), buffer :: t()}
38+
39+
@opaque t :: %__MODULE__{
40+
latency: non_neg_integer(),
41+
store: PacketStore.t(),
42+
state: :initial_wait | :timer_set | :timer_not_set
43+
}
44+
45+
@enforce_keys [:latency]
46+
defstruct @enforce_keys ++
47+
[
48+
store: %PacketStore{},
49+
state: :initial_wait
50+
]
51+
52+
@doc """
53+
Creates a new `t:#{inspect(__MODULE__)}.t/0`.
54+
"""
55+
@spec new(options()) :: t()
56+
def new(opts \\ []) do
57+
%__MODULE__{
58+
latency: opts[:latency] || @default_latency_ms
59+
}
60+
end
61+
62+
@doc """
63+
Places a packet in the JitterBuffer.
64+
65+
Note: The initial latency timer will be set after the first packet is inserted into the buffer.
66+
If you want to start it at your own discretion, schedule a `handle_timeout/1` call prior to that.
67+
"""
68+
@spec insert(t(), Packet.t()) :: result()
69+
def insert(buffer, packet)
70+
71+
def insert(%{state: :initial_wait} = buffer, packet) do
72+
{buffer, timer} = maybe_set_timer(buffer)
73+
{_result, buffer} = try_insert_packet(buffer, packet)
74+
75+
{[], timer, buffer}
76+
end
77+
78+
def insert(buffer, packet) do
79+
case try_insert_packet(buffer, packet) do
80+
{:ok, buffer} -> send_packets(buffer)
81+
{:error, buffer} -> {[], nil, buffer}
82+
end
83+
end
84+
85+
@doc """
86+
Flushes all remaining packets and resets the JitterBuffer.
87+
88+
Note: After flushing, the rollover counter is reset to `0`.
89+
"""
90+
@spec flush(t()) :: result()
91+
def flush(buffer) do
92+
packets =
93+
buffer.store
94+
|> PacketStore.dump()
95+
|> handle_missing_packets()
96+
97+
{packets, nil, %__MODULE__{latency: buffer.latency}}
98+
end
99+
100+
@doc """
101+
Handles the end of a previously set timer.
102+
"""
103+
@spec handle_timeout(t()) :: result()
104+
def handle_timeout(buffer) do
105+
%__MODULE__{buffer | state: :timer_not_set} |> send_packets()
106+
end
107+
108+
@spec try_insert_packet(t(), Packet.t()) :: {:ok | :error, t()}
109+
defp try_insert_packet(buffer, packet) do
110+
case PacketStore.insert(buffer.store, packet) do
111+
{:ok, store} -> {:ok, %__MODULE__{buffer | store: store}}
112+
{:error, :late_packet} -> {:error, buffer}
113+
end
114+
end
115+
116+
@spec send_packets(t()) :: result()
117+
defp send_packets(%{store: store} = buffer) do
118+
# Flush packets that stayed in queue longer than latency and any gaps before them
119+
{too_old_packets, store} = PacketStore.flush_older_than(store, buffer.latency)
120+
# Additionally, flush packets as long as there are no gaps
121+
{gapless_packets, store} = PacketStore.flush_ordered(store)
122+
123+
packets =
124+
too_old_packets
125+
|> Stream.concat(gapless_packets)
126+
|> handle_missing_packets()
127+
128+
{buffer, timer} = maybe_set_timer(%__MODULE__{buffer | store: store})
129+
130+
{packets, timer, buffer}
131+
end
132+
133+
@spec handle_missing_packets(Enumerable.t(Packet.t() | nil)) :: [Packet.t()]
134+
defp handle_missing_packets(packets) do
135+
# TODO: nil -- missing packet (maybe owner should be notified about that)
136+
Enum.reject(packets, &is_nil/1)
137+
end
138+
139+
@spec maybe_set_timer(t()) :: {t(), timer()}
140+
defp maybe_set_timer(buffer)
141+
142+
defp maybe_set_timer(%{state: :initial_wait} = buffer) do
143+
case PacketStore.first_packet_timestamp(buffer.store) do
144+
# If we're inserting the very first packet, set the initial latency timer
145+
nil -> {buffer, buffer.latency}
146+
_ts -> {buffer, nil}
147+
end
148+
end
149+
150+
defp maybe_set_timer(%{state: :timer_not_set} = buffer) do
151+
case PacketStore.first_packet_timestamp(buffer.store) do
152+
nil ->
153+
{buffer, nil}
154+
155+
timestamp_ms ->
156+
since_insertion = System.monotonic_time(:millisecond) - timestamp_ms
157+
send_after_time = max(0, buffer.latency - since_insertion)
158+
159+
{%__MODULE__{buffer | state: :timer_set}, send_after_time}
160+
end
161+
end
162+
163+
defp maybe_set_timer(%{state: :timer_set} = buffer), do: {buffer, nil}
164+
end

0 commit comments

Comments
 (0)