1use js_sys::{Array, Object, Reflect, Uint8Array};
2use openipc_core::{
3 ChannelId, FrameLayout, MockRtpPipeline, PayloadRouteId, ReceiverBatch, ReceiverBatchOptions,
4 ReceiverRuntime,
5};
6use wasm_bindgen::prelude::*;
7
8use crate::js::{elapsed_ms, now_ms, raw_payload_object, set_number};
9use crate::video::{rtp_status_object, video_frame_object};
10
11const MOCK_VIDEO_ROUTE_ID: PayloadRouteId = PayloadRouteId::new(1);
12const MOCK_KEY_SLOT: u64 = 0;
13
14#[wasm_bindgen]
15pub struct OpenIpcMockRtpPipeline {
17 pipeline: MockRtpPipeline,
18}
19
20#[wasm_bindgen]
21impl OpenIpcMockRtpPipeline {
22 #[wasm_bindgen(constructor)]
23 pub fn new(width: u16, height: u16, fps: u16) -> OpenIpcMockRtpPipeline {
25 OpenIpcMockRtpPipeline {
26 pipeline: MockRtpPipeline::new(width, height, fps),
27 }
28 }
29
30 #[wasm_bindgen(js_name = nextFrame, unchecked_return_type = "OpenIpcMockFrame")]
31 pub fn next_frame(&mut self) -> Result<Object, JsValue> {
33 let frame = self
34 .pipeline
35 .next_frame()
36 .map_err(|err| JsValue::from_str(&format!("mock RTP failed: {err:?}")))?;
37 let object = Object::new();
38 Reflect::set(
39 &object,
40 &JsValue::from_str("width"),
41 &JsValue::from_f64(f64::from(frame.width)),
42 )?;
43 Reflect::set(
44 &object,
45 &JsValue::from_str("height"),
46 &JsValue::from_f64(f64::from(frame.height)),
47 )?;
48 Reflect::set(
49 &object,
50 &JsValue::from_str("frameIndex"),
51 &JsValue::from_str(&frame.frame_index.to_string()),
52 )?;
53 Reflect::set(
54 &object,
55 &JsValue::from_str("timestamp"),
56 &JsValue::from_f64(f64::from(frame.timestamp)),
57 )?;
58 Reflect::set(
59 &object,
60 &JsValue::from_str("rtpPackets"),
61 &JsValue::from_f64(frame.rtp_packets as f64),
62 )?;
63 Reflect::set(
64 &object,
65 &JsValue::from_str("rtpBytes"),
66 &JsValue::from_f64(frame.rtp_bytes as f64),
67 )?;
68 Reflect::set(
69 &object,
70 &JsValue::from_str("rgba"),
71 &Uint8Array::from(frame.rgba.as_slice()),
72 )?;
73 Ok(object)
74 }
75}
76
77#[wasm_bindgen]
78pub struct OpenIpcMockPayloadRuntime {
83 runtime: ReceiverRuntime,
84 packet_seq: u64,
85}
86
87#[wasm_bindgen]
88impl OpenIpcMockPayloadRuntime {
89 #[wasm_bindgen(constructor)]
90 pub fn new(channel_id: u32) -> OpenIpcMockPayloadRuntime {
92 OpenIpcMockPayloadRuntime {
93 runtime: ReceiverRuntime::with_mock_video_route(
94 FrameLayout::WithFcs,
95 MOCK_VIDEO_ROUTE_ID,
96 ChannelId::new(channel_id),
97 MOCK_KEY_SLOT,
98 ),
99 packet_seq: 0,
100 }
101 }
102
103 #[wasm_bindgen(js_name = setRtpReorderEnabled)]
104 pub fn set_rtp_reorder_enabled(&mut self, enabled: bool) {
106 self.runtime.set_rtp_reorder_enabled(enabled);
107 }
108
109 #[wasm_bindgen(
110 js_name = pushPayloadProfiled,
111 unchecked_return_type = "OpenIpcRxTransferProfile"
112 )]
113 pub fn push_payload_profiled(&mut self, payload: &[u8]) -> Result<Object, JsValue> {
115 let total_start = now_ms();
116 let pipeline_start = now_ms();
117 let batch = self
118 .runtime
119 .push_mock_payload(
120 self.runtime.video_runtime(),
121 self.packet_seq,
122 payload,
123 &ReceiverBatchOptions::default(),
124 )
125 .map_err(|err| JsValue::from_str(&format!("mock payload rejected: {err}")))?;
126 self.packet_seq = self.packet_seq.wrapping_add(1);
127 let pipeline_ms = elapsed_ms(pipeline_start);
128 profiled_batch_object(
129 batch,
130 payload.len(),
131 0.0,
132 pipeline_ms,
133 elapsed_ms(total_start),
134 )
135 }
136}
137
138fn profiled_batch_object(
139 batch: ReceiverBatch,
140 transfer_len: usize,
141 parse_ms: f64,
142 pipeline_ms: f64,
143 total_ms: f64,
144) -> Result<Object, JsValue> {
145 let counters = batch.counters;
146 let frames = frame_objects_array(batch.frames)?;
147 let raw_payloads = raw_payload_array(batch.raw_payloads)?;
148 let rtp_status = rtp_status_object(batch.rtp_status, batch.rtp_reorder_status)?;
149
150 let object = Object::new();
151 Reflect::set(&object, &JsValue::from_str("frames"), &frames)?;
152 Reflect::set(&object, &JsValue::from_str("rawPayloads"), &raw_payloads)?;
153 Reflect::set(
154 &object,
155 &JsValue::from_str("mavlinkPayloads"),
156 &raw_payloads,
157 )?;
158 Reflect::set(&object, &JsValue::from_str("rtpStatus"), &rtp_status)?;
159 set_number(
160 &object,
161 "rawPayloadCount",
162 counters.raw_payload_count as f64,
163 )?;
164 set_number(
165 &object,
166 "rawPayloadBytes",
167 counters.raw_payload_bytes as f64,
168 )?;
169 set_number(&object, "transferBytes", transfer_len as f64)?;
170 set_number(&object, "packets", counters.packets.max(1) as f64)?;
171 set_number(
172 &object,
173 "acceptedPackets",
174 counters.accepted_packets.max(1) as f64,
175 )?;
176 set_number(&object, "droppedPackets", counters.dropped_packets as f64)?;
177 set_number(&object, "crcDropped", counters.crc_dropped as f64)?;
178 set_number(&object, "icvDropped", counters.icv_dropped as f64)?;
179 set_number(&object, "reportDropped", counters.report_dropped as f64)?;
180 set_number(&object, "ignoredFrames", counters.ignored_frames as f64)?;
181 set_number(&object, "sessions", counters.sessions as f64)?;
182 set_number(&object, "wfbPayloads", counters.wfb_payloads as f64)?;
183 set_number(&object, "rtpPackets", counters.rtp_packets as f64)?;
184 set_number(&object, "videoFrames", counters.video_frames as f64)?;
185 set_number(
186 &object,
187 "mavlinkPayloadCount",
188 counters.raw_payload_count as f64,
189 )?;
190 set_number(&object, "mavlinkBytes", counters.raw_payload_bytes as f64)?;
191 set_number(&object, "parseMs", parse_ms)?;
192 set_number(&object, "pipelineMs", pipeline_ms)?;
193 set_number(&object, "totalMs", total_ms)?;
194 Ok(object)
195}
196
197fn frame_objects_array(frames: Vec<openipc_core::DepacketizedFrame>) -> Result<Array, JsValue> {
198 let out = Array::new();
199 for frame in frames {
200 out.push(&video_frame_object(frame)?.into());
201 }
202 Ok(out)
203}
204
205fn raw_payload_array(payloads: Vec<openipc_core::RoutePayload>) -> Result<Array, JsValue> {
206 let out = Array::new();
207 for payload in payloads {
208 out.push(&raw_payload_object(payload)?.into());
209 }
210 Ok(out)
211}