libpulse_binding/context/
scache.rs

1// Copyright 2017 Lyndon Brown
2//
3// This file is part of the PulseAudio Rust language binding.
4//
5// Licensed under the MIT license or the Apache license (version 2.0), at your option. You may not
6// copy, modify, or distribute this file except in compliance with said license. You can find copies
7// of these licenses either in the LICENSE-MIT and LICENSE-APACHE files, or alternatively at
8// <http://opensource.org/licenses/MIT> and <http://www.apache.org/licenses/LICENSE-2.0>
9// respectively.
10//
11// Portions of documentation are copied from the LGPL 2.1+ licensed PulseAudio C headers on a
12// fair-use basis, as discussed in the overall project readme (available in the git repository).
13
14//! Sample cache mechanism.
15//!
16//! # Overview
17//!
18//! The sample cache provides a simple way of overcoming high network latencies and reducing
19//! bandwidth. Instead of streaming a sound precisely when it should be played, it is stored on the
20//! server and only the command to start playing it needs to be sent.
21//!
22//! # Creation
23//!
24//! To create a sample, the normal stream API is used (see [`stream`]). The function
25//! [`Stream::connect_upload()`] will make sure the stream is stored as a sample on the server.
26//!
27//! To complete the upload, [`Stream::finish_upload()`] is called and the sample will receive the
28//! same name as the stream. If the upload should be aborted, simply call [`Stream::disconnect()`].
29//!
30//! # Playing samples
31//!
32//! To play back a sample, simply call [`Context::play_sample()`]:
33//!
34//! ```rust,ignore
35//! extern crate libpulse_binding as pulse;
36//!
37//! use pulse::volume;
38//!
39//! //...
40//!
41//! let o = my_context.play_sample(
42//!     "sample2",            // Name of my sample
43//!     None,                 // Use default sink
44//!     volume::VOLUME_NORM,  // Full volume
45//!     None                  // Don’t need a callback
46//! );
47//! ```
48//!
49//! # Removing samples
50//!
51//! When a sample is no longer needed, it should be removed on the server to save resources. The
52//! sample is deleted using [`Context::remove_sample()`].
53//!
54//! [`stream`]: mod@crate::stream
55//! [`Stream::connect_upload()`]: crate::stream::Stream::connect_upload
56//! [`Stream::finish_upload()`]: crate::stream::Stream::finish_upload
57//! [`Stream::disconnect()`]: crate::stream::Stream::disconnect
58
59use std::os::raw::{c_char, c_void};
60use std::ffi::CString;
61use std::ptr::null;
62use super::{ContextInternal, Context};
63use crate::def;
64use crate::callbacks::{box_closure_get_capi_ptr, get_su_capi_params, get_su_callback};
65use crate::{operation::Operation, volume::Volume, proplist::Proplist};
66
67impl Context {
68    /// Removes a sample from the sample cache.
69    ///
70    /// Returns an operation object which may be used to cancel the operation while it is running.
71    ///
72    /// The callback must accept a `bool`, which indicates success.
73    ///
74    /// Panics if the underlying C function returns a null pointer.
75    pub fn remove_sample<F>(&mut self, name: &str, callback: F) -> Operation<dyn FnMut(bool)>
76        where F: FnMut(bool) + 'static
77    {
78        // Warning: New CStrings will be immediately freed if not bound to a variable, leading to
79        // as_ptr() giving dangling pointers!
80        let c_name = CString::new(name).unwrap();
81
82        let cb_data = box_closure_get_capi_ptr::<dyn FnMut(bool)>(Box::new(callback));
83        let ptr = unsafe { capi::pa_context_remove_sample(self.ptr, c_name.as_ptr(),
84            Some(super::success_cb_proxy), cb_data) };
85        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
86    }
87
88    /// Plays a sample from the sample cache to the specified device.
89    ///
90    /// If the specified device is `None` use the default sink.
91    ///
92    /// # Params
93    ///
94    /// * `name`: Name of the sample to play.
95    /// * `dev`: Sink to play this sample on, or `None` for default.
96    /// * `volume`: Volume to play this sample with, or `None` to leave the decision about the
97    ///   volume to the server side which is a good idea. [`Volume::INVALID`] has the same meaning
98    ///   as `None.
99    /// * `callback`: Optional success callback. It must accept a `bool`, which indicates success.
100    ///
101    /// Panics if the underlying C function returns a null pointer.
102    pub fn play_sample(&mut self, name: &str, dev: Option<&str>, volume: Option<Volume>,
103        callback: Option<Box<dyn FnMut(bool) + 'static>>) -> Operation<dyn FnMut(bool)>
104    {
105        // Warning: New CStrings will be immediately freed if not bound to a variable, leading to
106        // as_ptr() giving dangling pointers!
107        let c_name = CString::new(name).unwrap();
108        let c_dev = match dev {
109            Some(dev) => CString::new(dev).unwrap(),
110            None => CString::new("").unwrap(),
111        };
112
113        let p_dev = dev.map_or(null::<c_char>(), |_| c_dev.as_ptr() as *const c_char);
114        let vol = volume.unwrap_or(Volume::INVALID);
115
116        let (cb_fn, cb_data): (Option<extern "C" fn(_, _, _)>, _) =
117            get_su_capi_params::<_, _>(callback, super::success_cb_proxy);
118        let ptr = unsafe {
119            capi::pa_context_play_sample(self.ptr, c_name.as_ptr(), p_dev, vol.0, cb_fn, cb_data)
120        };
121        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(bool)>)
122    }
123
124    /// Plays a sample from the sample cache to the specified device, allowing specification of a
125    /// property list for the playback stream.
126    ///
127    /// If the device is `None` use the default sink.
128    ///
129    /// # Params
130    ///
131    /// * `name`: Name of the sample to play.
132    /// * `dev`: Sink to play this sample on, or `None` for default.
133    /// * `volume`: Volume to play this sample with, or `None` to leave the decision about the
134    ///   volume to the server side which is a good idea. [`Volume::INVALID`] has the same meaning
135    ///   as `None.
136    /// * `proplist`: Property list for this sound. The property list of the cached entry will have
137    ///   this merged into it.
138    /// * `callback`: Optional success callback. It must accept an `u32` index value wrapper in a
139    ///   `Result`. The index is the index of the sink input object. `Err` is given instead on
140    ///   failure.
141    ///
142    /// Panics if the underlying C function returns a null pointer.
143    pub fn play_sample_with_proplist(&mut self, name: &str, dev: Option<&str>,
144        volume: Option<Volume>, proplist: &Proplist,
145        callback: Option<Box<dyn FnMut(Result<u32, ()>) + 'static>>)
146        -> Operation<dyn FnMut(Result<u32, ()>)>
147    {
148        // Warning: New CStrings will be immediately freed if not bound to a
149        // variable, leading to as_ptr() giving dangling pointers!
150        let c_name = CString::new(name).unwrap();
151        let c_dev = match dev {
152            Some(dev) => CString::new(dev).unwrap(),
153            None => CString::new("").unwrap(),
154        };
155
156        let p_dev = dev.map_or(null::<c_char>(), |_| c_dev.as_ptr() as *const c_char);
157        let vol = volume.unwrap_or(Volume::INVALID);
158
159        let (cb_fn, cb_data): (Option<extern "C" fn(_, _, _)>, _) =
160            get_su_capi_params::<_, _>(callback, play_sample_success_cb_proxy);
161        let ptr = unsafe {
162            capi::pa_context_play_sample_with_proplist(self.ptr, c_name.as_ptr(), p_dev, vol.0,
163                proplist.0.ptr, cb_fn, cb_data)
164        };
165        Operation::from_raw(ptr, cb_data as *mut Box<dyn FnMut(Result<u32, ()>)>)
166    }
167}
168
169/// Proxy for completion success callbacks.
170///
171/// Warning: This is for single-use cases only! It destroys the actual closure callback.
172extern "C"
173fn play_sample_success_cb_proxy(_: *mut ContextInternal, index: u32, userdata: *mut c_void) {
174    let index_actual = match index { def::INVALID_INDEX => Err(()), i => Ok(i) };
175    let _ = std::panic::catch_unwind(|| {
176        // Note, destroys closure callback after use - restoring outer box means it gets dropped
177        let mut callback = get_su_callback::<dyn FnMut(Result<u32, ()>)>(userdata);
178        (callback)(index_actual);
179    });
180}