vlc-rs/src/core.rs

708 lines
22 KiB
Rust

// Copyright (c) 2015 T. Okubo
// This file is part of vlc-rs.
// Licensed under the MIT license, see the LICENSE file.
use std::ptr;
use std::borrow::Cow;
use std::marker::PhantomData;
use std::ffi::CString;
use std::i32;
use std::convert::TryInto;
use libc::{c_void, c_char, c_int};
use vlc_sys as sys;
use crate::tools::{to_cstr, from_cstr, from_cstr_ref};
use crate::enums::*;
/// 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<(), ()> {
let cstr = to_cstr(name);
let result = unsafe{
sys::libvlc_add_intf(self.ptr, cstr.as_ptr())
};
if result == 0 { Ok(()) }
else { Err(()) }
}
/// 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{ptr: 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{ptr: 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: sys::VaList) {
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());
}
/// List of module description.
pub struct ModuleDescriptionList {
ptr: *mut sys::libvlc_module_description_t,
}
impl ModuleDescriptionList {
/// Returns raw pointer
pub fn raw(&self) -> *mut sys::libvlc_module_description_t {
self.ptr
}
}
impl Drop for ModuleDescriptionList {
fn drop(&mut self) {
unsafe{ sys::libvlc_module_description_list_release(self.ptr) };
}
}
impl<'a> IntoIterator for &'a ModuleDescriptionList {
type Item = ModuleDescriptionRef<'a>;
type IntoIter = ModuleDescriptionListIter<'a>;
fn into_iter(self) -> Self::IntoIter {
ModuleDescriptionListIter{ptr: self.ptr, _phantomdata: PhantomData}
}
}
pub struct ModuleDescriptionListIter<'a> {
ptr: *mut sys::libvlc_module_description_t,
_phantomdata: PhantomData<&'a sys::libvlc_module_description_t>,
}
/// Description of a module.
/// The strings are owned.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct ModuleDescription {
pub name: Option<String>,
pub shortname: Option<String>,
pub longname: Option<String>,
pub help: Option<String>,
}
/// Description of a module.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct ModuleDescriptionRef<'a> {
pub name: Option<Cow<'a, str>>,
pub shortname: Option<Cow<'a, str>>,
pub longname: Option<Cow<'a, str>>,
pub help: Option<Cow<'a, str>>,
}
impl<'a> Iterator for ModuleDescriptionListIter<'a> {
type Item = ModuleDescriptionRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
unsafe{
if self.ptr.is_null() {
return None;
}
let p = self.ptr;
self.ptr = (*p).p_next;
Some(ModuleDescriptionRef{
name: from_cstr_ref((*p).psz_name),
shortname: from_cstr_ref((*p).psz_shortname),
longname: from_cstr_ref((*p).psz_longname),
help: from_cstr_ref((*p).psz_help),
})
}
}
}
impl<'a> ModuleDescriptionRef<'a> {
/// Convert to owned strings.
pub fn into_owned(&'a self) -> ModuleDescription {
ModuleDescription {
name: self.name .as_ref().map(|s| s.clone().into_owned()),
shortname: self.shortname.as_ref().map(|s| s.clone().into_owned()),
longname: self.name .as_ref().map(|s| s.clone().into_owned()),
help: self.shortname.as_ref().map(|s| s.clone().into_owned()),
}
}
}
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>,
}
type EventCallback = dyn Fn(Event, VLCObject) + Send + 'static;
type EventCallbackBox = Box<EventCallback>;
struct EventCallbackAllocation {
raw: *mut c_void,
}
impl EventCallbackAllocation {
fn new<F>(callback: F) -> EventCallbackAllocation
where F: Fn(Event, VLCObject) + Send + 'static
{
let callback: Box<EventCallbackBox> = Box::new(Box::new(callback));
EventCallbackAllocation { raw: Box::into_raw(callback) as *mut c_void }
}
fn raw(&self) -> *mut c_void {
self.raw
}
unsafe fn reclaim(self) {
drop(Box::from_raw(self.raw as *mut EventCallbackBox));
}
}
fn attach_event_callback<F, A>(callback: F, attach: A) -> Result<EventCallbackAllocation, ()>
where F: Fn(Event, VLCObject) + Send + 'static,
A: FnOnce(*mut c_void) -> i32
{
let callback = EventCallbackAllocation::new(callback);
if attach(callback.raw()) == 0 {
Ok(callback)
}else{
unsafe { callback.reclaim(); }
Err(())
}
}
fn release_event_callback<D>(callback: &mut Option<EventCallbackAllocation>, detach: D)
where D: FnOnce(*mut c_void)
{
if let Some(callback) = callback.take() {
detach(callback.raw());
unsafe { callback.reclaim(); }
}
}
/// Owns an event callback registration.
///
/// Dropping this value detaches the callback and reclaims its allocation.
pub struct EventSubscription<'a> {
event_manager: *mut sys::libvlc_event_manager_t,
event_type: EventType,
callback: Option<EventCallbackAllocation>,
_phantomdata: ::std::marker::PhantomData<&'a sys::libvlc_event_manager_t>,
}
impl<'a> EventSubscription<'a> {
/// Detach the callback now. Dropping the subscription has the same effect.
pub fn detach(mut self) {
self.release();
}
fn release(&mut self) {
let event_manager = self.event_manager;
let event_type = self.event_type;
release_event_callback(&mut self.callback, |callback| unsafe {
sys::libvlc_event_detach(
event_manager, event_type as i32, Some(event_manager_callback), callback);
});
}
}
impl<'a> Drop for EventSubscription<'a> {
fn drop(&mut self) {
self.release();
}
}
impl<'a> EventManager<'a> {
pub fn attach<F>(&self, event_type: EventType, callback: F) -> Result<EventSubscription<'a>, ()>
where F: Fn(Event, VLCObject) + Send + 'static
{
let callback = attach_event_callback(callback, |raw| unsafe {
sys::libvlc_event_attach(
self.ptr, event_type as i32, Some(event_manager_callback),
raw)
})?;
Ok(EventSubscription{
event_manager: self.ptr,
event_type,
callback: Some(callback),
_phantomdata: ::std::marker::PhantomData,
})
}
/// 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: &EventCallbackBox = ::std::mem::transmute(data);
f(conv_event(pe), VLCObject{ ptr: (*pe).p_obj });
}
#[cfg(test)]
mod event_callback_tests {
use super::{attach_event_callback, release_event_callback, Event, VLCObject};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
struct DropCounter(Arc<AtomicUsize>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
struct DropRecorder(Arc<Mutex<Vec<&'static str>>>);
impl Drop for DropRecorder {
fn drop(&mut self) {
self.0.lock().unwrap().push("drop");
}
}
#[test]
fn failed_attachment_reclaims_the_callback() {
let dropped = Arc::new(AtomicUsize::new(0));
let drop_counter = DropCounter(dropped.clone());
let result = attach_event_callback(
move |_: Event, _: VLCObject| { let _ = &drop_counter; },
|_| -1,
);
assert!(result.is_err());
assert_eq!(dropped.load(Ordering::SeqCst), 1);
}
#[test]
fn release_detaches_before_reclaiming_the_callback() {
let events = Arc::new(Mutex::new(Vec::new()));
let drop_recorder = DropRecorder(events.clone());
let mut callback = Some(attach_event_callback(
move |_: Event, _: VLCObject| {
let _ = &drop_recorder;
},
|_| 0,
).unwrap());
release_event_callback(&mut callback, |_| events.lock().unwrap().push("detach"));
release_event_callback(&mut callback, |_| panic!("callback detached twice"));
assert!(callback.is_none());
assert_eq!(*events.lock().unwrap(), vec!["detach", "drop"]);
}
}
// 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
}
}