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}