1use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Value};
7
8use crate::{ipc::MpvIpcEvent, message_parser::json_to_value, MpvDataType, MpvError};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum EventEndFileReason {
13 Eof,
14 Stop,
15 Quit,
16 Error,
17 Redirect,
18 Unknown,
19 Unimplemented(String),
20}
21
22impl FromStr for EventEndFileReason {
23 type Err = ();
24
25 fn from_str(s: &str) -> Result<Self, Self::Err> {
26 match s {
27 "eof" => Ok(EventEndFileReason::Eof),
28 "stop" => Ok(EventEndFileReason::Stop),
29 "quit" => Ok(EventEndFileReason::Quit),
30 "error" => Ok(EventEndFileReason::Error),
31 "redirect" => Ok(EventEndFileReason::Redirect),
32 reason => Ok(EventEndFileReason::Unimplemented(reason.to_string())),
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[serde(rename_all = "kebab-case")]
39pub enum EventLogMessageLevel {
40 Info,
41 Warn,
42 Error,
43 Fatal,
44 Verbose,
45 Debug,
46 Trace,
47 Unimplemented(String),
48}
49
50impl FromStr for EventLogMessageLevel {
51 type Err = ();
52
53 fn from_str(s: &str) -> Result<Self, Self::Err> {
54 match s {
55 "info" => Ok(EventLogMessageLevel::Info),
56 "warn" => Ok(EventLogMessageLevel::Warn),
57 "error" => Ok(EventLogMessageLevel::Error),
58 "fatal" => Ok(EventLogMessageLevel::Fatal),
59 "verbose" => Ok(EventLogMessageLevel::Verbose),
60 "debug" => Ok(EventLogMessageLevel::Debug),
61 "trace" => Ok(EventLogMessageLevel::Trace),
62 level => Ok(EventLogMessageLevel::Unimplemented(level.to_string())),
63 }
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77#[serde(rename_all = "kebab-case")]
78pub enum Event {
79 StartFile {
80 playlist_entry_id: usize,
81 },
82 EndFile {
83 reason: EventEndFileReason,
84 playlist_entry_id: usize,
85 file_error: Option<String>,
86 playlist_insert_id: Option<usize>,
87 playlist_insert_num_entries: Option<usize>,
88 },
89 FileLoaded,
90 Seek,
91 PlaybackRestart,
92 Shutdown,
93 LogMessage {
94 prefix: String,
95 level: EventLogMessageLevel,
96 text: String,
97 },
98 Hook {
99 hook_id: usize,
100 },
101 GetPropertyReply,
102 SetPropertyReply,
103 CommandReply {
104 result: String,
105 },
106 ClientMessage {
107 args: Vec<String>,
108 },
109 VideoReconfig,
110 AudioReconfig,
111 PropertyChange {
112 id: Option<u64>,
113 name: String,
114 data: Option<MpvDataType>,
115 },
116 EventQueueOverflow,
117 None,
118
119 Idle,
121
122 Tick,
124
125 TracksChanged,
127
128 TrackSwitched,
130
131 Pause,
133
134 Unpause,
136
137 MetadataUpdate,
139
140 ChapterChange,
142
143 ScriptInputDispatch,
145
146 Unimplemented(Map<String, Value>),
148}
149
150macro_rules! get_key_as {
151 ($as_type:ident, $key:expr, $event:ident) => {{
152 let tmp = $event.get($key).ok_or(MpvError::MissingKeyInObject {
153 key: $key.to_owned(),
154 map: $event.clone(),
155 })?;
156
157 tmp.$as_type()
158 .ok_or(MpvError::ValueContainsUnexpectedType {
159 expected_type: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
160 received: tmp.clone(),
161 })?
162 }};
163}
164
165macro_rules! get_optional_key_as {
166 ($as_type:ident, $key:expr, $event:ident) => {{
167 if let Some(tmp) = $event.get($key) {
168 Some(
169 tmp.$as_type()
170 .ok_or(MpvError::ValueContainsUnexpectedType {
171 expected_type: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
172 received: tmp.clone(),
173 })?,
174 )
175 } else {
176 None
177 }
178 }};
179}
180
181pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, MpvError> {
192 let MpvIpcEvent(event) = raw_event;
193
194 event
195 .as_object()
196 .ok_or(MpvError::ValueContainsUnexpectedType {
197 expected_type: "object".to_owned(),
198 received: event.clone(),
199 })
200 .and_then(|event| {
201 let event_name = get_key_as!(as_str, "event", event);
202
203 match event_name {
204 "start-file" => parse_start_file(event),
205 "end-file" => parse_end_file(event),
206 "file-loaded" => Ok(Event::FileLoaded),
207 "seek" => Ok(Event::Seek),
208 "playback-restart" => Ok(Event::PlaybackRestart),
209 "shutdown" => Ok(Event::Shutdown),
210 "log-message" => parse_log_message(event),
211 "hook" => parse_hook(event),
212
213 "client-message" => parse_client_message(event),
222 "video-reconfig" => Ok(Event::VideoReconfig),
223 "audio-reconfig" => Ok(Event::AudioReconfig),
224 "property-change" => parse_property_change(event),
225 "tick" => Ok(Event::Tick),
226 "idle" => Ok(Event::Idle),
227 "tracks-changed" => Ok(Event::TracksChanged),
228 "track-switched" => Ok(Event::TrackSwitched),
229 "pause" => Ok(Event::Pause),
230 "unpause" => Ok(Event::Unpause),
231 "metadata-update" => Ok(Event::MetadataUpdate),
232 "chapter-change" => Ok(Event::ChapterChange),
233 _ => Ok(Event::Unimplemented(event.to_owned())),
234 }
235 })
236}
237
238fn parse_start_file(event: &Map<String, Value>) -> Result<Event, MpvError> {
239 let playlist_entry_id = get_key_as!(as_u64, "playlist_entry_id", event) as usize;
240
241 Ok(Event::StartFile { playlist_entry_id })
242}
243
244fn parse_end_file(event: &Map<String, Value>) -> Result<Event, MpvError> {
245 let reason = get_key_as!(as_str, "reason", event);
246 let playlist_entry_id = get_key_as!(as_u64, "playlist_entry_id", event) as usize;
247 let file_error = get_optional_key_as!(as_str, "file_error", event).map(|s| s.to_string());
248 let playlist_insert_id =
249 get_optional_key_as!(as_u64, "playlist_insert_id", event).map(|i| i as usize);
250 let playlist_insert_num_entries =
251 get_optional_key_as!(as_u64, "playlist_insert_num_entries", event).map(|i| i as usize);
252
253 Ok(Event::EndFile {
254 reason: reason
255 .parse()
256 .unwrap_or(EventEndFileReason::Unimplemented(reason.to_string())),
257 playlist_entry_id,
258 file_error,
259 playlist_insert_id,
260 playlist_insert_num_entries,
261 })
262}
263
264fn parse_log_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
265 let prefix = get_key_as!(as_str, "prefix", event).to_owned();
266 let level = get_key_as!(as_str, "level", event);
267 let text = get_key_as!(as_str, "text", event).to_owned();
268
269 Ok(Event::LogMessage {
270 prefix,
271 level: level
272 .parse()
273 .unwrap_or(EventLogMessageLevel::Unimplemented(level.to_string())),
274 text,
275 })
276}
277
278fn parse_hook(event: &Map<String, Value>) -> Result<Event, MpvError> {
279 let hook_id = get_key_as!(as_u64, "hook_id", event) as usize;
280 Ok(Event::Hook { hook_id })
281}
282
283fn parse_client_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
284 let args = get_key_as!(as_array, "args", event)
285 .iter()
286 .map(|arg| {
287 arg.as_str()
288 .ok_or(MpvError::ValueContainsUnexpectedType {
289 expected_type: "string".to_owned(),
290 received: arg.clone(),
291 })
292 .map(|s| s.to_string())
293 })
294 .collect::<Result<Vec<String>, MpvError>>()?;
295 Ok(Event::ClientMessage { args })
296}
297
298fn parse_property_change(event: &Map<String, Value>) -> Result<Event, MpvError> {
299 let id = get_optional_key_as!(as_u64, "id", event);
300 let property_name = get_key_as!(as_str, "name", event);
301 let data = event.get("data").map(json_to_value).transpose()?;
302
303 Ok(Event::PropertyChange {
304 id,
305 name: property_name.to_string(),
306 data,
307 })
308}