// Copyright (c) 2015 T. Okubo // This file is part of vlc-rs. // Licensed under the MIT license, see the LICENSE file. use crate::enums::*; use crate::tools::{from_cstr, from_cstr_ref, linked_list_iter, to_cstr}; use libc::{c_char, c_int, c_void}; use std::borrow::Cow; use std::convert::TryInto; use std::ffi::CString; use std::fmt; use std::i32; use std::ptr; use vlc_sys as sys; #[derive(Debug)] pub struct InternalError; impl fmt::Display for InternalError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "libvlc internal error") } } impl std::error::Error for InternalError {} linked_list_iter!( libvlc_module_description, // C raw type module_description, // Rust base struct name { name: (String, str), shortname: (String, str), longname: (String, str), help: (String, str), } // fields ); linked_list_iter!( libvlc_audio_output, // C raw type audio_output, // Rust base struct name { name: (String, str), description: (String, str), } // fields ); linked_list_iter!( libvlc_audio_output_device, // C raw type audio_output_device, // Rust base struct name { device: (String, str), description: (String, str), } // fields ); /// Retrieve libvlc version. pub fn version() -> String { unsafe { from_cstr_ref(sys::libvlc_get_version()) .unwrap() .into_owned() } } /// Retrieve libvlc compiler version. pub fn compiler() -> String { unsafe { from_cstr_ref(sys::libvlc_get_compiler()) .unwrap() .into_owned() } } pub struct Instance { pub(crate) ptr: *mut sys::libvlc_instance_t, } unsafe impl Send for Instance {} impl Instance { /// Create and initialize a libvlc instance with specified args. /// Note: args.len() has to be less or equal to i32::MAX /// Note: libvlc discourages using arguments as these are not guaranteed to be stable between different versions of libvlc pub fn with_args(args: Option<Vec<String>>) -> Option<Instance> { let args_c_ptr: Vec<*const c_char>; let args_c: Vec<CString>; if let Some(argv) = args { args_c = argv .into_iter() .map(|x| CString::new(x).expect("Error: Unexpected null byte")) .collect(); args_c_ptr = args_c.iter().map(|x| x.as_ptr()).collect(); } else { args_c_ptr = Vec::new(); } unsafe { let p = if args_c_ptr.is_empty() { sys::libvlc_new(0, ptr::null()) } else { sys::libvlc_new(args_c_ptr.len() as i32, args_c_ptr.as_ptr()) }; if p.is_null() { return None; } Some(Instance { ptr: p }) } } /// Create and initialize a libvlc instance. pub fn new() -> Option<Instance> { Instance::with_args(None) } /// Try to start a user interface for the libvlc instance. pub fn add_intf(&self, name: &str) -> Result<(), InternalError> { let cstr = to_cstr(name); let result = unsafe { sys::libvlc_add_intf(self.ptr, cstr.as_ptr()) }; if result == 0 { Ok(()) } else { Err(InternalError) } } /// Sets the application name. /// LibVLC passes this as the user agent string when a protocol requires it. pub fn set_user_agent(&self, name: &str, http: &str) { unsafe { sys::libvlc_set_user_agent(self.ptr, to_cstr(name).as_ptr(), to_cstr(http).as_ptr()); } } /// Waits until an interface causes the instance to exit. pub fn wait(&self) { unsafe { sys::libvlc_wait(self.ptr) }; } /// Sets some meta-information about the application. pub fn set_app_id(&self, id: &str, version: &str, icon: &str) { unsafe { sys::libvlc_set_app_id( self.ptr, to_cstr(id).as_ptr(), to_cstr(version).as_ptr(), to_cstr(icon).as_ptr(), ); } } /// Returns a list of audio filters that are available. pub fn audio_filter_list_get(&self) -> Option<ModuleDescriptionList> { unsafe { let p = sys::libvlc_audio_filter_list_get(self.ptr); if p.is_null() { None } else { Some(ModuleDescriptionList::new(p)) } } } /// Returns a list of video filters that are available. pub fn video_filter_list_get(&self) -> Option<ModuleDescriptionList> { unsafe { let p = sys::libvlc_video_filter_list_get(self.ptr); if p.is_null() { None } else { Some(ModuleDescriptionList::new(p)) } } } /// Returns the VLM event manager pub fn vlm_event_manager<'a>(&'a self) -> EventManager<'a> { unsafe { let p = sys::libvlc_vlm_get_event_manager(self.ptr); assert!(!p.is_null()); EventManager { ptr: p, _phantomdata: ::std::marker::PhantomData, } } } /// Set logging callback pub fn set_log<F: Fn(LogLevel, Log, Cow<str>) + Send + 'static>(&self, f: F) { let cb: Box<Box<dyn Fn(LogLevel, Log, Cow<str>) + Send + 'static>> = Box::new(Box::new(f)); unsafe { sys::libvlc_log_set(self.ptr, Some(logging_cb), Box::into_raw(cb) as *mut _); } } /// Returns raw pointer pub fn raw(&self) -> *mut sys::libvlc_instance_t { self.ptr } } impl Drop for Instance { fn drop(&mut self) { unsafe { sys::libvlc_release(self.ptr); } } } const BUF_SIZE: usize = 1024; // Write log message to the buffer by vsnprintf. unsafe extern "C" fn logging_cb( data: *mut c_void, level: c_int, ctx: *const sys::libvlc_log_t, fmt: *const c_char, args: *mut sys::__va_list_tag, ) { let f: &Box<dyn Fn(LogLevel, Log, Cow<str>) + Send + 'static> = ::std::mem::transmute(data); let mut buf: [c_char; BUF_SIZE] = [0; BUF_SIZE]; sys::vsnprintf(buf.as_mut_ptr(), BUF_SIZE.try_into().unwrap(), fmt, args); f( (level as u32).into(), Log { ptr: ctx }, from_cstr_ref(buf.as_ptr()).unwrap(), ); } pub fn errmsg() -> Option<String> { unsafe { from_cstr(sys::libvlc_errmsg()) } } pub fn clearerr() { unsafe { sys::libvlc_clearerr() }; } #[derive(Clone, Debug)] pub enum Event { MediaMetaChanged(Meta), MediaSubItemAdded, MediaDurationChanged(i64), MediaParsedChanged(i32), MediaFreed, MediaStateChanged(State), MediaSubItemTreeAdded, MediaPlayerMediaChanged, MediaPlayerNothingSpecial, MediaPlayerOpening, MediaPlayerBuffering(f32), MediaPlayerPlaying, MediaPlayerPaused, MediaPlayerStopped, MediaPlayerForward, MediaPlayerBackward, MediaPlayerEndReached, MediaPlayerEncounteredError, MediaPlayerTimeChanged, MediaPlayerPositionChanged(f32), MediaPlayerSeekableChanged, MediaPlayerPausableChanged, MediaPlayerTitleChanged, MediaPlayerSnapshotTaken, MediaPlayerLengthChanged, MediaPlayerVout, MediaPlayerScrambledChanged, MediaListItemAdded, MediaListWillAddItem, MediaListItemDeleted, MediaListWillDeleteItem, MediaListViewItemAdded, MediaListViewWillAddItem, MediaListViewItemDeleted, MediaListViewWillDeleteItem, MediaListPlayerPlayed, MediaListPlayerNextItemSet, MediaListPlayerStopped, MediaDiscovererStarted, MediaDiscovererEnded, VlmMediaAdded(Option<String>, Option<String>), VlmMediaRemoved(Option<String>, Option<String>), VlmMediaChanged(Option<String>, Option<String>), VlmMediaInstanceStarted(Option<String>, Option<String>), VlmMediaInstanceStopped(Option<String>, Option<String>), VlmMediaInstanceStatusInit(Option<String>, Option<String>), VlmMediaInstanceStatusOpening(Option<String>, Option<String>), VlmMediaInstanceStatusPlaying(Option<String>, Option<String>), VlmMediaInstanceStatusPause(Option<String>, Option<String>), VlmMediaInstanceStatusEnd(Option<String>, Option<String>), VlmMediaInstanceStatusError(Option<String>, Option<String>), } pub struct EventManager<'a> { pub(crate) ptr: *mut sys::libvlc_event_manager_t, pub(crate) _phantomdata: ::std::marker::PhantomData<&'a sys::libvlc_event_manager_t>, } impl<'a> EventManager<'a> { pub fn attach<F>(&self, event_type: EventType, callback: F) -> Result<(), InternalError> where F: Fn(Event, VLCObject) + Send + 'static, { // Explicit type annotation is needed let callback: Box<Box<dyn Fn(Event, VLCObject) + Send + 'static>> = Box::new(Box::new(callback)); let result = unsafe { sys::libvlc_event_attach( self.ptr, event_type as i32, Some(event_manager_callback), Box::into_raw(callback) as *mut c_void, ) }; if result == 0 { Ok(()) } else { Err(InternalError) } } /// Returns raw pointer pub fn raw(&self) -> *mut sys::libvlc_event_manager_t { self.ptr } } unsafe extern "C" fn event_manager_callback(pe: *const sys::libvlc_event_t, data: *mut c_void) { let f: &Box<dyn Fn(Event, VLCObject) + Send + 'static> = ::std::mem::transmute(data); f(conv_event(pe), VLCObject { ptr: (*pe).p_obj }); } // Convert c-style libvlc_event_t to Event fn conv_event(pe: *const sys::libvlc_event_t) -> Event { let event_type: EventType = (unsafe { (*pe).type_ } as u32).into(); match event_type { EventType::MediaMetaChanged => unsafe { Event::MediaMetaChanged((*pe).u.media_meta_changed.meta_type.into()) }, EventType::MediaSubItemAdded => Event::MediaSubItemAdded, EventType::MediaDurationChanged => unsafe { Event::MediaDurationChanged((*pe).u.media_duration_changed.new_duration) }, EventType::MediaParsedChanged => unsafe { Event::MediaParsedChanged((*pe).u.media_parsed_changed.new_status) }, EventType::MediaFreed => Event::MediaFreed, EventType::MediaStateChanged => unsafe { let new_state: sys::libvlc_state_t = (*pe).u.media_state_changed.new_state.try_into().unwrap(); Event::MediaStateChanged(new_state.into()) }, EventType::MediaSubItemTreeAdded => Event::MediaSubItemTreeAdded, EventType::MediaPlayerMediaChanged => Event::MediaPlayerMediaChanged, EventType::MediaPlayerNothingSpecial => Event::MediaPlayerNothingSpecial, EventType::MediaPlayerOpening => Event::MediaPlayerOpening, EventType::MediaPlayerBuffering => unsafe { Event::MediaPlayerBuffering((*pe).u.media_player_buffering.new_cache) }, EventType::MediaPlayerPlaying => Event::MediaPlayerPlaying, EventType::MediaPlayerPaused => Event::MediaPlayerPaused, EventType::MediaPlayerStopped => Event::MediaPlayerStopped, EventType::MediaPlayerForward => Event::MediaPlayerForward, EventType::MediaPlayerBackward => Event::MediaPlayerBackward, EventType::MediaPlayerEndReached => Event::MediaPlayerEndReached, EventType::MediaPlayerEncounteredError => Event::MediaPlayerEncounteredError, EventType::MediaPlayerTimeChanged => Event::MediaPlayerTimeChanged, EventType::MediaPlayerPositionChanged => unsafe { Event::MediaPlayerPositionChanged((*pe).u.media_player_position_changed.new_position) }, EventType::MediaPlayerSeekableChanged => Event::MediaPlayerSeekableChanged, EventType::MediaPlayerPausableChanged => Event::MediaPlayerPausableChanged, EventType::MediaPlayerTitleChanged => Event::MediaPlayerTitleChanged, EventType::MediaPlayerSnapshotTaken => Event::MediaPlayerSnapshotTaken, EventType::MediaPlayerLengthChanged => Event::MediaPlayerLengthChanged, EventType::MediaPlayerVout => Event::MediaPlayerVout, EventType::MediaPlayerScrambledChanged => Event::MediaPlayerScrambledChanged, EventType::MediaListItemAdded => Event::MediaListItemAdded, EventType::MediaListWillAddItem => Event::MediaListWillAddItem, EventType::MediaListItemDeleted => Event::MediaListItemDeleted, EventType::MediaListWillDeleteItem => Event::MediaListWillDeleteItem, EventType::MediaListViewItemAdded => Event::MediaListViewItemAdded, EventType::MediaListViewWillAddItem => Event::MediaListViewWillAddItem, EventType::MediaListViewItemDeleted => Event::MediaListViewItemDeleted, EventType::MediaListViewWillDeleteItem => Event::MediaListViewWillDeleteItem, EventType::MediaListPlayerPlayed => Event::MediaListPlayerPlayed, EventType::MediaListPlayerNextItemSet => Event::MediaListPlayerNextItemSet, EventType::MediaListPlayerStopped => Event::MediaListPlayerStopped, EventType::MediaDiscovererStarted => Event::MediaDiscovererStarted, EventType::MediaDiscovererEnded => Event::MediaDiscovererEnded, EventType::VlmMediaAdded => unsafe { Event::VlmMediaAdded( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaRemoved => unsafe { Event::VlmMediaRemoved( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaChanged => unsafe { Event::VlmMediaChanged( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStarted => unsafe { Event::VlmMediaInstanceStarted( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStopped => unsafe { Event::VlmMediaInstanceStopped( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStatusInit => unsafe { Event::VlmMediaInstanceStatusInit( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStatusOpening => unsafe { Event::VlmMediaInstanceStatusOpening( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStatusPlaying => unsafe { Event::VlmMediaInstanceStatusPlaying( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStatusPause => unsafe { Event::VlmMediaInstanceStatusPause( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStatusEnd => unsafe { Event::VlmMediaInstanceStatusEnd( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, EventType::VlmMediaInstanceStatusError => unsafe { Event::VlmMediaInstanceStatusError( from_cstr((*pe).u.vlm_media_event.psz_instance_name), from_cstr((*pe).u.vlm_media_event.psz_media_name), ) }, } } pub struct VLCObject { ptr: *mut c_void, } impl VLCObject { /// Returns raw pointer pub fn raw(&self) -> *mut c_void { self.ptr } } pub struct Log { pub(crate) ptr: *const sys::libvlc_log_t, } impl Log { /// Returns raw pointer pub fn raw(&self) -> *const sys::libvlc_log_t { self.ptr } }