libmpv_client/
event.rs

1//! The various [`Event`]s and their payloads which may be sent by mpv.
2
3use std::ffi::{c_void, CStr};
4use libmpv_client_sys::{mpv_event, mpv_event_client_message, mpv_event_command, mpv_event_end_file, mpv_event_hook, mpv_event_id, mpv_event_id_MPV_EVENT_AUDIO_RECONFIG, mpv_event_id_MPV_EVENT_CLIENT_MESSAGE, mpv_event_id_MPV_EVENT_COMMAND_REPLY, mpv_event_id_MPV_EVENT_END_FILE, mpv_event_id_MPV_EVENT_FILE_LOADED, mpv_event_id_MPV_EVENT_GET_PROPERTY_REPLY, mpv_event_id_MPV_EVENT_HOOK, mpv_event_id_MPV_EVENT_IDLE, mpv_event_id_MPV_EVENT_LOG_MESSAGE, mpv_event_id_MPV_EVENT_PLAYBACK_RESTART, mpv_event_id_MPV_EVENT_PROPERTY_CHANGE, mpv_event_id_MPV_EVENT_QUEUE_OVERFLOW, mpv_event_id_MPV_EVENT_SEEK, mpv_event_id_MPV_EVENT_SET_PROPERTY_REPLY, mpv_event_id_MPV_EVENT_SHUTDOWN, mpv_event_id_MPV_EVENT_START_FILE, mpv_event_id_MPV_EVENT_TICK, mpv_event_id_MPV_EVENT_VIDEO_RECONFIG, mpv_event_log_message, mpv_event_property, mpv_event_start_file, mpv_format};
5use crate::*;
6use crate::error::error_to_result_code;
7use crate::types::traits::MpvRecvInternal;
8
9/// [`Event`] IDs for use with [`Handle::request_event()`].
10pub struct EventId(pub(crate) mpv_event_id);
11
12impl EventId {
13    /// Requests [`Event::Shutdown`].
14    pub const SHUTDOWN: EventId = EventId(mpv_event_id_MPV_EVENT_SHUTDOWN);
15    /// Requests [`Event::LogMessage`].
16    pub const LOG_MESSAGE: EventId = EventId(mpv_event_id_MPV_EVENT_LOG_MESSAGE);
17    /// Requests [`Event::GetPropertyReply`].
18    pub const GET_PROPERTY_REPLY: EventId = EventId(mpv_event_id_MPV_EVENT_GET_PROPERTY_REPLY);
19    /// Requests [`Event::SetPropertyReply`].
20    pub const SET_PROPERTY_REPLY: EventId = EventId(mpv_event_id_MPV_EVENT_SET_PROPERTY_REPLY);
21    /// Requests [`Event::CommandReply`].
22    pub const COMMAND_REPLY: EventId = EventId(mpv_event_id_MPV_EVENT_COMMAND_REPLY);
23    /// Requests [`Event::StartFile`].
24    pub const START_FILE: EventId = EventId(mpv_event_id_MPV_EVENT_START_FILE);
25    /// Requests [`Event::EndFile`].
26    pub const END_FILE: EventId = EventId(mpv_event_id_MPV_EVENT_END_FILE);
27    /// Requests [`Event::FileLoaded`].
28    pub const FILE_LOADED: EventId = EventId(mpv_event_id_MPV_EVENT_FILE_LOADED);
29    /// Requests [`Event::Idle`].
30    pub const IDLE: EventId = EventId(mpv_event_id_MPV_EVENT_IDLE);
31    /// Requests [`Event::Tick`].
32    pub const TICK: EventId = EventId(mpv_event_id_MPV_EVENT_TICK);
33    /// Requests [`Event::ClientMessage`].
34    pub const CLIENT_MESSAGE: EventId = EventId(mpv_event_id_MPV_EVENT_CLIENT_MESSAGE);
35    /// Requests [`Event::VideoReconfig`].
36    pub const VIDEO_RECONFIG: EventId = EventId(mpv_event_id_MPV_EVENT_VIDEO_RECONFIG);
37    /// Requests [`Event::AudioReconfig`].
38    pub const AUDIO_RECONFIG: EventId = EventId(mpv_event_id_MPV_EVENT_AUDIO_RECONFIG);
39    /// Requests [`Event::Seek`].
40    pub const SEEK: EventId = EventId(mpv_event_id_MPV_EVENT_SEEK);
41    /// Requests [`Event::PlaybackRestart`].
42    pub const PLAYBACK_RESTART: EventId = EventId(mpv_event_id_MPV_EVENT_PLAYBACK_RESTART);
43    /// Requests [`Event::PropertyChange`].
44    pub const PROPERTY_CHANGE: EventId = EventId(mpv_event_id_MPV_EVENT_PROPERTY_CHANGE);
45    /// Requests [`Event::QueueOverflow`].
46    pub const QUEUE_OVERFLOW: EventId = EventId(mpv_event_id_MPV_EVENT_QUEUE_OVERFLOW);
47    /// Requests [`Event::Hook`].
48    pub const HOOK: EventId = EventId(mpv_event_id_MPV_EVENT_HOOK);
49}
50
51/// The possible log levels that mpv can apply to log messages.
52#[derive(Debug)]
53pub enum LogLevel {
54    /// disable logging
55    None,
56    /// critical/aborting errors
57    Fatal,
58    /// simple errors
59    Error,
60    /// possible problems
61    Warn,
62    /// informational message
63    Info,
64    /// noisy informational message
65    Verbose,
66    /// very noisy technical information
67    Debug,
68    /// extremely noisy
69    Trace,
70}
71
72const LOG_LEVEL_NONE: &CStr = c"no";
73const LOG_LEVEL_FATAL: &CStr = c"fatal";
74const LOG_LEVEL_ERROR: &CStr = c"error";
75const LOG_LEVEL_WARN: &CStr = c"warn";
76const LOG_LEVEL_INFO: &CStr = c"info";
77const LOG_LEVEL_VERBOSE: &CStr = c"v";
78const LOG_LEVEL_DEBUG: &CStr = c"debug";
79const LOG_LEVEL_TRACE: &CStr = c"trace";
80
81impl LogLevel {
82    pub(crate) fn to_cstr(&self) -> &CStr {
83        match self {
84            LogLevel::None => LOG_LEVEL_NONE,
85            LogLevel::Fatal => LOG_LEVEL_FATAL,
86            LogLevel::Error => LOG_LEVEL_ERROR,
87            LogLevel::Warn => LOG_LEVEL_WARN,
88            LogLevel::Info => LOG_LEVEL_INFO,
89            LogLevel::Verbose => LOG_LEVEL_VERBOSE,
90            LogLevel::Debug => LOG_LEVEL_DEBUG,
91            LogLevel::Trace => LOG_LEVEL_TRACE,
92        }
93    }
94}
95
96/// Possible reasons for an [`Event::EndFile`].
97#[derive(Debug)]
98pub enum EndFileReason {
99    /// The end of file was reached.
100    ///
101    /// Sometimes this may also happen on incomplete or corrupted files, or if the network connection was interrupted when playing a remote file.
102    /// It also happens if the playback range was restricted with `--end` or `--frames` or similar.
103    Eof,
104    /// Playback was stopped by an external action (e.g. playlist controls).
105    Stop,
106    /// Playback was stopped by the quit command or player shutdown.
107    Quit,
108    /// Some kind of error happened that lead to playback abort.
109    ///
110    /// Does not necessarily happen on incomplete or broken files (in these cases, both [`EndFileReason::Error`] or [`EndFileReason::Eof`] are possible).
111    Error(Error),
112    /// The file was a playlist or similar.
113    ///
114    /// When the playlist is read, its entries will be appended to the playlist after the entry of the current file, the entry of the current file is removed, and an [`Event::EndFile`] is sent with [`EndFile.reason`](field@EndFile::reason) set to [`EndFileReason::Redirect`].
115    /// Then playback continues with the playlist contents.
116    Redirect,
117}
118
119/// Events that may be received from [`Handle::wait_event()`].
120///
121/// Some are just informational, while some contain additional data and some are responses to mpv commands.
122#[derive(Debug)]
123pub enum Event {
124    /// Nothing happened. Happens on timeouts or sporadic wakeups.
125    None,
126    /// Happens when the player quits. The player enters a state where it tries to disconnect all clients.
127    ///
128    /// Most requests to the player will fail, and the client should react to this accordingly;
129    /// a [`Handle`] should return execution from whatever context it was passed its [`mpv_handle`],
130    /// while a [`Client`] should call [`Client::destroy`] and quit as soon as possible.
131    Shutdown,
132    /// Happens when mpv receives a log message that matches the level filter set up with [`Handle::request_log_messages()`].
133    LogMessage(LogMessage),
134    /// Reply to a `mpv_get_property_async()` request.
135    GetPropertyReply(GetPropertyReply),
136    /// Reply to a `mpv_set_property_async()` request.
137    SetPropertyReply(SetPropertyReply),
138    /// Reply to a `mpv_command_async()` or `mpv_command_node_async()` request.
139    CommandReply(CommandReply),
140    /// Notification before playback start of a file (before the file is loaded).
141    StartFile(StartFile),
142    /// Notification after playback end (after the file was unloaded).
143    EndFile(EndFile),
144    /// Notification when the file has been loaded (headers were read etc.), and decoding starts.
145    FileLoaded,
146    /// Idle mode was entered.
147    ///
148    /// In this mode, no file is played, and the playback core waits for new commands.
149    ///
150    /// (The command line player normally quits instead of entering idle mode, unless `--idle` was specified. If mpv was started with [`Handle::create()`], idle mode is enabled by default.)
151    #[deprecated = "This is equivalent to using mpv_observe_property() on the `idle-active` property. The event is redundant, and might be removed in the far future. As a further warning, this event is not necessarily sent at the right point anymore (at the start of the program), while the property behaves correctly."]
152    Idle,
153    /// Sent every time after a video frame is displayed.
154    ///
155    /// Note that currently this will be sent in lower frequency if there is no video, or playback is paused - but that will be removed in the future, and it will be restricted to video frames only.
156    #[deprecated = "Use `Handle::observe_property()` with relevant properties instead (such as `playback-time`)."]
157    Tick,
158    /// Triggered by the `script-message` input command.
159    ///
160    /// The command uses the first argument of the command as a client name (see [`Handle::client_name()`]) to dispatch the message and passes along all arguments starting from the second argument as strings.
161    ClientMessage(ClientMessage),
162    /// Happens after video changed in some way. This can happen on resolution changes, pixel format changes, or video filter changes. The event is sent after the video filters and the VO are reconfigured. Applications embedding a mpv window should listen to this event to resize the window if needed.
163    ///
164    /// Note that this event can happen sporadically, and you should check yourself whether the video parameters really changed before doing something expensive.
165    VideoReconfig,
166    /// Similar to [`Event::VideoReconfig`].
167    ///
168    /// This is relatively uninteresting because there is no such thing as audio output embedding.
169    AudioReconfig,
170    /// Happens when a seek was initiated. Playback stops.
171    ///
172    /// Usually it will resume with [`Event::PlaybackRestart`] as soon as the seek is finished.
173    Seek,
174    /// There was a discontinuity of some sort (like a seek), and playback was reinitialized.
175    ///
176    /// Usually happens at the start of playback and after seeking. The main purpose is allowing the client to detect when a seek request is finished.
177    PlaybackRestart,
178    /// Event sent when a property observed with [`Handle::observe_property()`] is changed.
179    PropertyChange(PropertyChange),
180    /// Happens if the internal per-[`mpv_handle`] ringbuffer overflows, and at least 1 event had to be dropped.
181    ///
182    /// This can happen if the client doesn't read the event queue quickly enough with [`Handle::wait_event()`], or if the client makes a very large number of asynchronous calls at once.
183    ///
184    /// Event delivery will continue normally once this event was returned (this forces the client to empty the queue completely).
185    QueueOverflow,
186    /// Triggered if a hook handler was registered with [`Handle::hook_add()`], and the hook is invoked.
187    ///
188    /// If you receive this, you **must** handle it and continue the hook with [`Handle::hook_continue()`].
189    Hook(Hook),
190}
191
192/// Details provided to [`Event::LogMessage`].
193#[derive(Debug)]
194pub struct LogMessage {
195    /// The level of this log message.
196    pub level: LogLevel,
197    /// The module prefix, identifies the sender of the message.
198    ///
199    /// As a special case, if the message buffer overflows, this will be set to the string "overflow" (which doesn't appear as a prefix otherwise), and the text field will contain an informative message.
200    pub prefix: String,
201    /// The log message. It consists of 1 line of text and is terminated with a newline character.
202    pub text: String,
203}
204
205/// Details provided to [`Event::GetPropertyReply`].
206#[derive(Debug)]
207pub struct GetPropertyReply {
208    /// Value of the property, or an error if one occurred.
209    pub value: Result<PropertyValue>,
210    /// Name of the property.
211    pub name: String,
212    /// `userdata` value passed to the mpv request which generated this event, if provided.
213    // TODO: Update doc with link to async function when implemented.
214    pub userdata: u64,
215}
216
217/// Details provided to [`Event::SetPropertyReply`].
218#[derive(Debug)]
219pub struct SetPropertyReply {
220    /// The error setting the property, if any. mpv may also report a success code, which is retained in Ok(i32).
221    pub error: Result<i32>,
222    /// `userdata` value passed to the mpv request which generated this event, if provided.
223    // TODO: Update doc with link to async function when implemented.
224    pub userdata: u64,
225}
226
227/// Details provided to [`Event::CommandReply`].
228#[derive(Debug)]
229pub struct CommandReply {
230    /// Result of a command (which may be `Node::None` even on success depending on the command), or an error if one occurred.
231    pub result: Result<Node>,
232    /// `userdata` value passed to the mpv request which generated this event, if provided.
233    // TODO: Update doc with link to async function when implemented.
234    pub userdata: u64,
235}
236
237/// Details provided to [`Event::StartFile`].
238#[derive(Debug)]
239pub struct StartFile {
240    /// Playlist entry ID of the file being loaded now.
241    pub playlist_entry_id: i64,
242}
243
244/// Details provided to [`Event::EndFile`].
245#[derive(Debug)]
246pub struct EndFile {
247    /// The reason why the file ended.
248    pub reason: EndFileReason,
249    /// Playlist entry ID of the file that was being played or attempted to be played.
250    ///
251    /// This has the same value as the [`StartFile.playlist_entry_id`](field@StartFile::playlist_entry_id) field in the corresponding [`Event::StartFile`] event.
252    pub playlist_entry_id: i64,
253    /// If loading ended, because the playlist entry to be played was for example a playlist, and the current playlist entry is replaced with a number of other entries.
254    ///
255    /// This may happen at least with [`EndFileReason::Redirect`] (other event types may use this for similar but different purposes in the future).
256    /// In this case, [`playlist_insert_id`] will be set to the playlist entry ID of the first inserted entry, and [`playlist_insert_num_entries`] to the total number of inserted playlist entries.
257    /// Note this in this specific case, the ID of the last inserted entry is [`playlist_insert_id`] + [`playlist_insert_num_entries`] - 1.
258    /// Beware that depending on circumstances, you may observe the new playlist entries before seeing the event (e.g. reading the `playlist` property or getting a property change notification before receiving the event).
259    ///
260    /// [`playlist_insert_id`]: field@EndFile::playlist_insert_id
261    /// [`playlist_insert_num_entries`]: field@EndFile::playlist_insert_num_entries
262    pub playlist_insert_id: i64,
263    /// See [`playlist_insert_id`]. Only non-0 if [`playlist_insert_id`] is valid. Never negative.
264    ///
265    /// [`playlist_insert_id`]: field@EndFile::playlist_insert_id
266    pub playlist_insert_num_entries: i32,
267}
268
269/// Details provided to [`Event::ClientMessage`].
270///
271/// Arbitrary arguments chosen by the sender of the message. What these arguments mean is up to the sender and receiver.
272#[derive(Debug)]
273pub struct ClientMessage(pub Vec<String>);
274
275/// Details provided to [`Event::PropertyChange`].
276#[derive(Debug)]
277pub struct PropertyChange {
278    /// Name of the property.
279    pub name: String,
280    /// New value of the property, or [`PropertyValue::None`] if an error occurred.
281    ///
282    /// Note that mpv does not propagate error details for the [`PropertyChange`] event.
283    /// Any [`Err`] result will be a [`RustError`](error::RustError) created while attempting to parse the data.
284    pub value: Result<PropertyValue>,
285    /// `userdata` value passed to the mpv request which generated this event, if provided.
286    // TODO: Update doc with link to async function when implemented.
287    pub userdata: u64,
288}
289
290/// Details provided to [`Event::Hook`].
291#[derive(Debug)]
292pub struct Hook {
293    /// The hook name as passed to [`Handle::hook_add()`].
294    pub name: String,
295    /// Internal ID which must be passed to [`Handle::hook_continue()`].
296    pub id: u64,
297    /// `userdata` value passed to the mpv request which generated this event, if provided.
298    // TODO: Update doc with link to async function when implemented.
299    pub userdata: u64,
300}
301
302impl Event {
303    pub(crate) fn from_ptr(ptr: *const mpv_event) -> Result<Event> {
304        check_null!(ptr);
305        let event = unsafe { *ptr };
306
307        match event.event_id {
308            libmpv_client_sys::mpv_event_id_MPV_EVENT_NONE => Ok(Event::None),
309            libmpv_client_sys::mpv_event_id_MPV_EVENT_SHUTDOWN => Ok(Event::Shutdown),
310            libmpv_client_sys::mpv_event_id_MPV_EVENT_LOG_MESSAGE => Ok(Event::LogMessage(LogMessage::from_event(event)?)),
311            libmpv_client_sys::mpv_event_id_MPV_EVENT_GET_PROPERTY_REPLY => Ok(Event::GetPropertyReply(GetPropertyReply::from_event(event)?)),
312            libmpv_client_sys::mpv_event_id_MPV_EVENT_SET_PROPERTY_REPLY => Ok(Event::SetPropertyReply(SetPropertyReply::from_event(event)?)),
313            libmpv_client_sys::mpv_event_id_MPV_EVENT_COMMAND_REPLY => Ok(Event::CommandReply(CommandReply::from_event(event)?)),
314            libmpv_client_sys::mpv_event_id_MPV_EVENT_START_FILE => Ok(Event::StartFile(StartFile::from_event(event)?)),
315            libmpv_client_sys::mpv_event_id_MPV_EVENT_END_FILE => Ok(Event::EndFile(EndFile::from_event(event)?)),
316            libmpv_client_sys::mpv_event_id_MPV_EVENT_FILE_LOADED => Ok(Event::FileLoaded),
317            #[allow(deprecated)]
318            libmpv_client_sys::mpv_event_id_MPV_EVENT_IDLE => Ok(Event::Idle),
319            #[allow(deprecated)]
320            libmpv_client_sys::mpv_event_id_MPV_EVENT_TICK => Ok(Event::Tick),
321            libmpv_client_sys::mpv_event_id_MPV_EVENT_CLIENT_MESSAGE => Ok(Event::ClientMessage(ClientMessage::from_event(event)?)),
322            libmpv_client_sys::mpv_event_id_MPV_EVENT_VIDEO_RECONFIG => Ok(Event::VideoReconfig),
323            libmpv_client_sys::mpv_event_id_MPV_EVENT_AUDIO_RECONFIG => Ok(Event::AudioReconfig),
324            libmpv_client_sys::mpv_event_id_MPV_EVENT_SEEK => Ok(Event::Seek),
325            libmpv_client_sys::mpv_event_id_MPV_EVENT_PLAYBACK_RESTART => Ok(Event::PlaybackRestart),
326            libmpv_client_sys::mpv_event_id_MPV_EVENT_PROPERTY_CHANGE => Ok(Event::PropertyChange(PropertyChange::from_event(event)?)),
327            libmpv_client_sys::mpv_event_id_MPV_EVENT_QUEUE_OVERFLOW => Ok(Event::QueueOverflow),
328            libmpv_client_sys::mpv_event_id_MPV_EVENT_HOOK => Ok(Event::Hook(Hook::from_event(event)?)),
329            _ => unimplemented!(),
330        }
331    }
332}
333
334impl LogMessage {
335    fn from_event(event: mpv_event) -> Result<Self> {
336        check_null!(event.data);
337        let event_log_message = unsafe { *(event.data as *const mpv_event_log_message) };
338
339        let level = match event_log_message.log_level {
340            libmpv_client_sys::mpv_log_level_MPV_LOG_LEVEL_FATAL => LogLevel::Fatal,
341            libmpv_client_sys::mpv_log_level_MPV_LOG_LEVEL_ERROR => LogLevel::Error,
342            libmpv_client_sys::mpv_log_level_MPV_LOG_LEVEL_WARN => LogLevel::Warn,
343            libmpv_client_sys::mpv_log_level_MPV_LOG_LEVEL_INFO => LogLevel::Info,
344            libmpv_client_sys::mpv_log_level_MPV_LOG_LEVEL_V => LogLevel::Verbose,
345            libmpv_client_sys::mpv_log_level_MPV_LOG_LEVEL_DEBUG => LogLevel::Debug,
346            libmpv_client_sys::mpv_log_level_MPV_LOG_LEVEL_TRACE => LogLevel::Trace,
347            _ => unimplemented!()
348        };
349
350        check_null!(event_log_message.prefix);
351        let prefix = unsafe { CStr::from_ptr(event_log_message.prefix) }.to_str()?.to_string();
352
353        check_null!(event_log_message.text);
354        let text = unsafe { CStr::from_ptr(event_log_message.text) }.to_str()?.to_string();
355
356        Ok(Self { level, prefix, text })
357    }
358}
359
360impl GetPropertyReply {
361    fn from_event(event: mpv_event) -> Result<Self> {
362        check_null!(event.data);
363        let event_prop = unsafe { *(event.data as *const mpv_event_property) };
364
365        check_null!(event_prop.name);
366        let name = unsafe { CStr::from_ptr(event_prop.name).to_str()?.to_string() };
367
368        let value = error_to_result_code(event.error)
369            .and_then(|_| {
370                unsafe { PropertyValue::from_mpv(event_prop.format, event_prop.data) }
371            });
372
373        let userdata = event.reply_userdata;
374
375        Ok(Self { value, name, userdata })
376    }
377}
378
379impl SetPropertyReply {
380    fn from_event(event: mpv_event) -> Result<Self> {
381        let error = error_to_result_code(event.error);
382
383        let userdata = event.reply_userdata;
384
385        Ok(Self { error, userdata })
386    }
387}
388
389impl CommandReply {
390    fn from_event(event: mpv_event) -> Result<Self> {
391        check_null!(event.data);
392        let event_command = unsafe { *(event.data as *const mpv_event_command) };
393
394        let result = error_to_result_code(event.error)
395            .and_then(|_| {
396                unsafe { Node::from_node_ptr(&event_command.result) }
397            });
398
399        let userdata = event.reply_userdata;
400
401        Ok(Self { result, userdata })
402    }
403}
404
405impl ClientMessage {
406    fn from_event(event: mpv_event) -> Result<Self> {
407        check_null!(event.data);
408        let event_client_message = unsafe { *(event.data as *const mpv_event_client_message) };
409
410        let mut args = Vec::with_capacity(event_client_message.num_args as usize);
411
412        check_null!(event_client_message.args);
413        let event_args = unsafe { std::slice::from_raw_parts(event_client_message.args, event_client_message.num_args as usize) };
414
415        for event_arg in event_args {
416            check_null!(event_arg);
417            args.push(unsafe { CStr::from_ptr(*event_arg).to_str()?.to_string() });
418        }
419
420        Ok(Self(args))
421    }
422}
423
424impl StartFile {
425    fn from_event(event: mpv_event) -> Result<Self> {
426        check_null!(event.data);
427        let event_start_file = unsafe { *(event.data as *const mpv_event_start_file) };
428
429        Ok(Self { playlist_entry_id: event_start_file.playlist_entry_id })
430    }
431}
432
433impl EndFile {
434    fn from_event(event: mpv_event) -> Result<Self> {
435        check_null!(event.data);
436        let event_end_file = unsafe { *(event.data as *const mpv_event_end_file) };
437
438        let reason = match event_end_file.reason {
439            libmpv_client_sys::mpv_end_file_reason_MPV_END_FILE_REASON_EOF => EndFileReason::Eof,
440            libmpv_client_sys::mpv_end_file_reason_MPV_END_FILE_REASON_STOP => EndFileReason::Stop,
441            libmpv_client_sys::mpv_end_file_reason_MPV_END_FILE_REASON_QUIT => EndFileReason::Quit,
442            libmpv_client_sys::mpv_end_file_reason_MPV_END_FILE_REASON_ERROR => EndFileReason::Error(Error::from(event_end_file.error)),
443            libmpv_client_sys::mpv_end_file_reason_MPV_END_FILE_REASON_REDIRECT => EndFileReason::Redirect,
444            _ => unimplemented!(),
445        };
446
447        Ok(Self {
448            reason,
449            playlist_entry_id: event_end_file.playlist_entry_id,
450            playlist_insert_id: event_end_file.playlist_insert_id,
451            playlist_insert_num_entries: event_end_file.playlist_insert_num_entries,
452        })
453    }
454}
455
456impl PropertyChange {
457    fn from_event(event: mpv_event) -> Result<Self> {
458        check_null!(event.data);
459        let event_prop = unsafe { *(event.data as *const mpv_event_property) };
460
461        check_null!(event_prop.name);
462        let name = unsafe { CStr::from_ptr(event_prop.name).to_str()?.to_string() };
463
464        let value = unsafe { PropertyValue::from_mpv(event_prop.format, event_prop.data) };
465
466        let userdata = event.reply_userdata;
467
468        Ok(Self { value, name, userdata })
469    }
470}
471
472impl Hook {
473    fn from_event(event: mpv_event) -> Result<Self> {
474        check_null!(event.data);
475        let event_hook = unsafe { *(event.data as *const mpv_event_hook) };
476
477        check_null!(event_hook.name);
478        let name = unsafe { CStr::from_ptr(event_hook.name) }.to_str()?.to_string();
479
480        let id = event_hook.id;
481
482        let userdata = event.reply_userdata;
483
484        Ok(Self { name, id, userdata })
485    }
486}
487
488#[derive(Debug)]
489/// An enum of the possible values returned in a [`GetPropertyReply`] or a [`PropertyChange`].
490pub enum PropertyValue {
491    /// Sometimes used for empty values or errors. See [`Format::NONE`].
492    None,
493    /// A raw property string. See [`Format::STRING`].
494    String(String),
495    /// An OSD property string. See [`Format::OSD_STRING`].
496    OsdString(OsdString),
497    /// A flag property. See [`Format::FLAG`].
498    Flag(bool),
499    /// An int64 property. See [`Format::INT64`].
500    Int64(i64),
501    /// A double property. See [`Format::DOUBLE`].
502    Double(f64),
503    /// A [`Node`] property. See [`Format::NODE`].
504    Node(Node),
505    /// A [`NodeArray`] property. See [`Format::NODE_ARRAY`].
506    NodeArray(NodeArray),
507    /// A [`NodeMap`] property. See [`Format::NODE_MAP`].
508    NodeMap(NodeMap),
509    /// A [`ByteArray`] property. See [`Format::BYTE_ARRAY`].
510    ByteArray(ByteArray),
511}
512
513impl PropertyValue {
514    pub(crate) unsafe fn from_mpv(format: mpv_format, data: *mut c_void) -> Result<Self> {
515        match format {
516            libmpv_client_sys::mpv_format_MPV_FORMAT_NONE => Ok(Self::None),
517            libmpv_client_sys::mpv_format_MPV_FORMAT_STRING => Ok(Self::String(unsafe { String::from_ptr(data)? })),
518            libmpv_client_sys::mpv_format_MPV_FORMAT_OSD_STRING => Ok(Self::OsdString(unsafe { OsdString::from_ptr(data)? })),
519            libmpv_client_sys::mpv_format_MPV_FORMAT_FLAG => Ok(Self::Flag(unsafe { bool::from_ptr(data)? })),
520            libmpv_client_sys::mpv_format_MPV_FORMAT_INT64 => Ok(Self::Int64(unsafe { i64::from_ptr(data)? })),
521            libmpv_client_sys::mpv_format_MPV_FORMAT_DOUBLE => Ok(Self::Double(unsafe { f64::from_ptr(data)? })),
522            libmpv_client_sys::mpv_format_MPV_FORMAT_NODE => Ok(Self::Node(unsafe { Node::from_ptr(data)? })),
523            libmpv_client_sys::mpv_format_MPV_FORMAT_NODE_ARRAY => Ok(Self::NodeArray(unsafe { NodeArray::from_ptr(data)? })),
524            libmpv_client_sys::mpv_format_MPV_FORMAT_NODE_MAP => Ok(Self::NodeMap(unsafe { NodeMap::from_ptr(data)? })),
525            libmpv_client_sys::mpv_format_MPV_FORMAT_BYTE_ARRAY => Ok(Self::ByteArray(unsafe { ByteArray::from_ptr(data)? })),
526            _ => unimplemented!()
527        }
528    }
529}