-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcompositor_utils.ex
206 lines (178 loc) · 5.56 KB
/
compositor_utils.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
defmodule RecordingConverter.Compositor do
@moduledoc false
alias Membrane.LiveCompositor.Request
alias RecordingConverter.ReportParser
@text_margin 10
@letter_width 12
@output_width 1280
@output_height 720
@video_output_id "video_output_1"
@audio_output_id "audio_output_1"
@avatar_threshold_ns 1_000_000_000
@spec server_setup(binary) :: :start_locally | {:start_locally, String.t()}
def server_setup(compositor_path) do
compositor_path = compositor_path
if is_nil(compositor_path) do
:start_locally
else
{:start_locally, compositor_path}
end
end
@spec scene(any()) :: map()
def scene(children) do
%{
id: "tiles_0",
type: :tiles,
width: @output_width,
height: @output_height,
background_color_rgba: "#00000000",
children: children
}
end
@spec audio_output_id() :: String.t()
def audio_output_id(), do: @audio_output_id
@spec video_output_id() :: String.t()
def video_output_id(), do: @video_output_id
@spec generate_output_update(map(), number(), map()) :: [struct()]
def generate_output_update(tracks, timestamp, video_tracks_offset),
do: [
generate_video_output_update(tracks, timestamp, video_tracks_offset),
generate_audio_output_update(tracks, timestamp)
]
@spec schedule_unregister_video_output(number()) :: Request.UnregisterOutput.t() | struct()
def schedule_unregister_video_output(schedule_time_ns),
do: %Request.UnregisterOutput{
output_id: @video_output_id,
schedule_time: Membrane.Time.nanoseconds(schedule_time_ns)
}
@spec schedule_unregister_audio_output(number()) :: Request.UnregisterOutput.t() | struct()
def schedule_unregister_audio_output(schedule_time_ns),
do: %Request.UnregisterOutput{
output_id: @audio_output_id,
schedule_time: Membrane.Time.nanoseconds(schedule_time_ns)
}
@spec schedule_unregister_input(number(), binary()) :: Request.UnregisterInput.t() | struct()
def schedule_unregister_input(schedule_time_ns, input_id),
do: %Request.UnregisterInput{
input_id: input_id,
schedule_time: Membrane.Time.nanoseconds(schedule_time_ns)
}
@spec register_image_action(String.t()) :: Request.RegisterImage.t() | struct()
def register_image_action(image_url) do
%Request.RegisterImage{
asset_type: "png",
image_id: "avatar_png",
url: image_url
}
end
defp generate_video_output_update(
%{"video" => video_tracks, "audio" => audio_tracks},
timestamp,
video_tracks_offset
)
when is_list(video_tracks) do
video_tracks_origin = Enum.map(video_tracks, fn track -> track["origin"] end)
avatar_tracks =
Enum.filter(
audio_tracks,
&should_have_avatar?(&1, timestamp, video_tracks_origin, video_tracks_offset)
)
avatars_config = Enum.map(avatar_tracks, &avatar_view/1)
video_tracks_config = Enum.map(video_tracks, &video_input_source_view/1)
%Request.UpdateVideoOutput{
output_id: @video_output_id,
schedule_time: Membrane.Time.nanoseconds(timestamp),
root: scene(video_tracks_config ++ avatars_config)
}
end
defp generate_audio_output_update(%{"audio" => audio_tracks}, timestamp)
when is_list(audio_tracks) do
%Request.UpdateAudioOutput{
output_id: @audio_output_id,
inputs: Enum.map(audio_tracks, &%{input_id: &1.id}),
schedule_time: Membrane.Time.nanoseconds(timestamp)
}
end
defp should_have_avatar?(
%{"origin" => origin} = track,
timestamp,
video_tracks_origin,
video_tracks_offset
) do
origin not in video_tracks_origin and
longer_than_treshold?(track, timestamp) and
not has_video_in_threshold?(origin, video_tracks_offset, timestamp)
end
defp longer_than_treshold?(%{"offset" => offset} = track, timestamp) do
ReportParser.calculate_track_end(track, offset) - timestamp > @avatar_threshold_ns
end
defp has_video_in_threshold?(origin, video_tracks_offset, timestamp) do
threshold = timestamp + @avatar_threshold_ns
next_video_offset =
video_tracks_offset
|> Map.get(origin, [])
|> Enum.find(threshold, &(&1 > timestamp))
next_video_offset < threshold
end
defp video_input_source_view(track) do
%{
type: :view,
children:
[
# TODO: fix after compositor update
# unnecessary rescaler
%{
type: :rescaler,
mode: "fit",
child: %{
type: :input_stream,
input_id: track.id
}
}
] ++ text_view(track["metadata"])
}
end
defp avatar_view(track) do
%{
type: :view,
children:
[
# TODO: fix after compositor update
# unnecessary rescaler
%{
type: :rescaler,
mode: "fit",
child: %{
type: :image,
image_id: "avatar_png"
}
}
] ++ text_view(track["metadata"])
}
end
defp text_view(%{"displayName" => label}) do
label_width = String.length(label) * @letter_width + @text_margin
[
%{
type: :view,
bottom: 20,
right: 20,
width: label_width,
height: 20,
background_color_rgba: "#000000FF",
children: [
%{type: :view},
%{
type: :text,
text: label,
align: "center",
width: label_width,
font_size: 20.0
},
%{type: :view}
]
}
]
end
defp text_view(_metadata), do: []
end