fzyzcjy / flutter_rust_bridge

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple.
https://fzyzcjy.github.io/flutter_rust_bridge/
MIT License
4.13k stars 285 forks source link

[Bug] V1.81.0 Codegen Error #1354

Closed The-Mr-L closed 1 year ago

The-Mr-L commented 1 year ago

Describe the bug

Hi :) well I just wanted to let you know that for some reason the latest codegen fails but using either 1.79.0 or 1.80.1 seems to work as expected. I have not yet had time to look into it myself. But I will report back if I at some point know why :)

Codegen logs with RUST_LOG=debug environment variable

2023/09/10 11:46:07 [ERROR] panicked at 'called `Result::unwrap()` on an `Err` value: Error("lex error")', .cargo\registry\src\index.crates.io-6f17d22bba15001f\flutter_rust_bridge_codegen-1.81.0\src\parser\source_graph.rs:359:83

To Reproduce

No response

Expected behavior

No response

Generated binding code

No response

OS

No response

Version of flutter_rust_bridge_codegen

No response

Flutter info

No response

Version of clang++

No response

Version of ffigen

No response

Additional context

No response

fzyzcjy commented 1 year ago

Hi, could you please provide a minimal reproducible sample? "lex error" seems like it does not understand something.

fzyzcjy commented 1 year ago

Btw, is this related https://github.com/fzyzcjy/flutter_rust_bridge/issues/1328

The-Mr-L commented 1 year ago

wellI can understand why you ask for more context since the error seams to be a general one. it was just me thinking you might be able to tell from what has changed since last release. but it is fair. and btw the latest codegen is running fine in a basic template app on my machine. but in my project it panics. for now I will just stick with 1.80.1. and I will try to make the minimal example hopefully this upcoming week or look into the error myself . thanks again for maintaining this repo!

The-Mr-L commented 1 year ago

and I don't think the error is related to the issue mentioned

fzyzcjy commented 1 year ago

Hi, it is because "lex error" can mean a lot of things... For example, which exact piece of code causes the lex error? With a minimal reproducible sample, it will be easier to realize that!

The-Mr-L commented 1 year ago

Yes for sure , I understand :)

The-Mr-L commented 1 year ago

Okay I am sorry I have not yet had the time to make a test case for this, and since it only seams to be me having the issue I will lose this for now, and report back later if I can't fix it on my end. with hopefully a more specific error message :)

The-Mr-L commented 1 year ago

:)

fzyzcjy commented 12 months ago

Sure, take your time and feel free to post the issues!

erikas-taroza commented 12 months ago

I am running into this issue as well. The codegen works fine in v1.80.1. When I get to v1.81.0, it breaks. Maybe whatever got expanded is invalid?

Logs ``` 2023/09/18 21:23:53 [WARN] Do not use `lib.rs` as a Rust input. Please put code to be generated in an `api.rs` or similar. 2023/09/18 21:23:53 [DEBUG] configs=[Opts { rust_input_path: "/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust/src/lib.rs", dart_output_path: "/home/erikas/ROOT/code/flutter-plugins/simple_audio/./lib/src/bridge_generated.dart", dart_decl_output_path: Some("/home/erikas/ROOT/code/flutter-plugins/simple_audio/./lib/src/bridge_definitions.dart"), c_output_path: ["/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust/src/bridge_generated.h"], rust_crate_dir: "/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust", rust_output_path: "/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust/src/bridge_generated.rs", class_name: "SimpleAudio", dart_format_line_length: 80, dart_enums_style: true, skip_add_mod_to_lib: false, llvm_path: ["/opt/homebrew/opt/llvm", "/usr/local/opt/llvm", "/usr/lib/llvm-9", "/usr/lib/llvm-10", "/usr/lib/llvm-11", "/usr/lib/llvm-12", "/usr/lib/llvm-13", "/usr/lib/llvm-14", "/usr/lib/", "/usr/lib64/", "C:/Program Files/llvm", "C:/msys64/mingw64"], llvm_compiler_opts: "", manifest_path: "/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust/Cargo.toml", dart_root: Some("/home/erikas/ROOT/code/flutter-plugins/simple_audio"), build_runner: true, block_index: BlockIndex(0), skip_deps_check: false, wasm_enabled: false, inline_rust: false, bridge_in_method: true, extra_headers: "", dart3: true, keep_going: false }] 2023/09/18 21:23:53 [INFO] Running cargo expand in '/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust' 2023/09/18 21:23:53 [DEBUG] execute command: bin=cargo args="expand --theme=none --ugly" current_dir=Some("/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust") cmd=cd "/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust" && "cargo" "expand" "--theme=none" "--ugly" 2023/09/18 21:23:54 [DEBUG] command=cd "/home/erikas/ROOT/code/flutter-plugins/simple_audio/./rust" && "cargo" "expand" "--theme=none" "--ugly" stdout=#![feature(prelude_import)] #[prelude_import] use std::prelude::rust_2021::*; #[macro_use] extern crate std; // This file is a part of simple_audio // Copyright (c) 2022-2023 Erikas Taroza // // This program is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with this program. // If not, see . mod audio { /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ // Enable logging from Rust to Android logcat. // `android_logger::init_once` can safely be called multiple times // but will only initialize once. // Start the decoding thread. // Reset the Linux/Windows media controllers. // --------------------------------- // SETTERS/GETTERS // --------------------------------- // --------------------------------- // PLAYBACK // --------------------------------- // --------------------------------- // CONTROLS // --------------------------------- // https://github.com/RustAudio/cpal/issues/720#issuecomment-1311813294 // You can change the file extension here for different formats ------v // https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/audio-samples.html // The following tests are to check the responsiveness. pub mod controls { use std::sync::{ atomic::AtomicBool, Arc, OnceLock, RwLock, RwLockReadGuard, }; use crossbeam::channel::{ unbounded, Receiver, RecvError, SendError, Sender, TryRecvError, }; use symphonia::core::io::MediaSource; use crate::utils::types::ProgressState; /// Use this to stop the decoder thread. pub static THREAD_KILLER: OnceLock, Receiver)>> = OnceLock::new(); /// Creates a getter and setter for an AtomicBool. macro_rules! getset_atomic_bool { ($name : ident, $setter_name : ident) => { pub fn $name(& self) -> bool { self.$name.load(std :: sync :: atomic :: Ordering :: SeqCst) } pub fn $setter_name(& self, value : bool) { self.$name.store(value, std :: sync :: atomic :: Ordering :: SeqCst) ; } } ; } /// Creates a getter and setter for an RwLock. macro_rules! getset_rwlock { ($name : ident, $setter_name : ident, $lock_type : ty) => { pub fn $name(& self) -> RwLockReadGuard < '_, $lock_type > { self.$name.read().unwrap() } pub fn $setter_name(& self, value : $lock_type) { * self.$name.write().unwrap() = value ; } } ; } pub struct EventHandler { sender: Sender, receiver: Receiver, } impl EventHandler { fn new() -> EventHandler { let (sender, receiver) = unbounded(); Self { sender, receiver } } pub fn send(&self, event: PlayerEvent) -> Result<(), SendError> { self.sender.send(event) } pub fn recv(&self) -> Result { self.receiver.recv() } pub fn try_recv(&self) -> Result { self.receiver.try_recv() } } pub struct Controls { event_handler: Arc>, is_playing: Arc, is_stopped: Arc, is_looping: Arc, is_normalizing: Arc, is_file_preloaded: Arc, volume: Arc>, seek_ts: Arc>>, progress: Arc>, } #[automatically_derived] impl ::core::clone::Clone for Controls { #[inline] fn clone(&self) -> Controls { Controls { event_handler: ::core::clone::Clone::clone(&self.event_handler), is_playing: ::core::clone::Clone::clone(&self.is_playing), is_stopped: ::core::clone::Clone::clone(&self.is_stopped), is_looping: ::core::clone::Clone::clone(&self.is_looping), is_normalizing: ::core::clone::Clone::clone(&self.is_normalizing), is_file_preloaded: ::core::clone::Clone::clone(&self.is_file_preloaded), volume: ::core::clone::Clone::clone(&self.volume), seek_ts: ::core::clone::Clone::clone(&self.seek_ts), progress: ::core::clone::Clone::clone(&self.progress), } } } impl Controls { pub fn event_handler(&self) -> RwLockReadGuard<'_, EventHandler> { self.event_handler.read().unwrap() } pub fn _set_event_handler(&self, value: EventHandler) { *self.event_handler.write().unwrap() = value; } pub fn is_playing(&self) -> bool { self.is_playing.load(std::sync::atomic::Ordering::SeqCst) } pub fn set_is_playing(&self, value: bool) { self.is_playing.store(value, std::sync::atomic::Ordering::SeqCst); } pub fn is_stopped(&self) -> bool { self.is_stopped.load(std::sync::atomic::Ordering::SeqCst) } pub fn set_is_stopped(&self, value: bool) { self.is_stopped.store(value, std::sync::atomic::Ordering::SeqCst); } pub fn is_looping(&self) -> bool { self.is_looping.load(std::sync::atomic::Ordering::SeqCst) } pub fn set_is_looping(&self, value: bool) { self.is_looping.store(value, std::sync::atomic::Ordering::SeqCst); } pub fn is_normalizing(&self) -> bool { self.is_normalizing.load(std::sync::atomic::Ordering::SeqCst) } pub fn set_is_normalizing(&self, value: bool) { self.is_normalizing.store(value, std::sync::atomic::Ordering::SeqCst); } pub fn is_file_preloaded(&self) -> bool { self.is_file_preloaded.load(std::sync::atomic::Ordering::SeqCst) } pub fn set_is_file_preloaded(&self, value: bool) { self.is_file_preloaded.store(value, std::sync::atomic::Ordering::SeqCst); } pub fn volume(&self) -> RwLockReadGuard<'_, f32> { self.volume.read().unwrap() } pub fn set_volume(&self, value: f32) { *self.volume.write().unwrap() = value; } pub fn seek_ts(&self) -> RwLockReadGuard<'_, Option> { self.seek_ts.read().unwrap() } pub fn set_seek_ts(&self, value: Option) { *self.seek_ts.write().unwrap() = value; } pub fn progress(&self) -> RwLockReadGuard<'_, ProgressState> { self.progress.read().unwrap() } pub fn set_progress(&self, value: ProgressState) { *self.progress.write().unwrap() = value; } } impl Default for Controls { fn default() -> Self { Controls { event_handler: Arc::new(RwLock::new(EventHandler::new())), is_playing: Arc::new(AtomicBool::new(false)), is_stopped: Arc::new(AtomicBool::new(true)), is_looping: Arc::new(AtomicBool::new(false)), is_normalizing: Arc::new(AtomicBool::new(false)), is_file_preloaded: Arc::new(AtomicBool::new(false)), volume: Arc::new(RwLock::new(1.0)), seek_ts: Arc::new(RwLock::new(None)), progress: Arc::new(RwLock::new(ProgressState { position: 0, duration: 0, })), } } } pub enum PlayerEvent { Open(Box, Arc), Play, Pause, Stop, /// Called by `cpal_output` in the event the device outputting /// audio was changed/disconnected. DeviceChanged, Preload(Box, Arc), PlayPreload, } } mod cpal_output { use std::sync::{atomic::AtomicBool, Arc, Condvar, Mutex}; use anyhow::Context; use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, Device, Stream, StreamConfig, }; use symphonia::core::audio::{ AudioBufferRef, SampleBuffer, SignalSpec, }; use crate::utils::blocking_rb::*; use super::{ controls::*, dsp::{normalizer::Normalizer, resampler::Resampler}, }; /// The default output volume is way too high. /// Multiplying the volume input by this number /// will help to reduce it. const BASE_VOLUME: f32 = 0.8; pub struct CpalOutput { pub stream: Stream, pub spec: SignalSpec, pub duration: u64, pub ring_buffer_reader: BlockingRb, ring_buffer_writer: BlockingRb, sample_buffer: SampleBuffer, resampler: Option>, is_stream_done: Arc<(Mutex, Condvar)>, normalizer: Normalizer, controls: Controls, } impl CpalOutput { /// Starts a new stream on the default device. pub fn new(controls: Controls, buffer_signal: Arc, spec: SignalSpec, duration: u64) -> anyhow::Result { let (device, config, ring_buffer_size) = Self::get_config(spec)?; let resampler: Option> = if false && spec.rate != config.sample_rate.0 { Some(Resampler::new(spec, config.sample_rate.0 as usize, duration)) } else { None }; let rb = BlockingRb::::new(ring_buffer_size); let rb_clone = rb.clone(); let ring_buffer_writer = rb.0; let ring_buffer_reader = rb.1; let sample_buffer = SampleBuffer::::new(duration, spec); let is_stream_done = Arc::new((Mutex::new(false), Condvar::new())); let stream = device.build_output_stream(&config, { let controls = controls.clone(); let is_stream_done = is_stream_done.clone(); move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { let buffering = buffer_signal.load(std::sync::atomic::Ordering::SeqCst); if (false && !controls.is_playing() && !controls.is_stopped()) || buffering { data.iter_mut().for_each(|s| *s = 0.0); if buffering { ring_buffer_reader.skip_all(); } return; } let written = ring_buffer_reader.read(data); if written.is_none() { let (mutex, cvar) = &*is_stream_done; *mutex.lock().unwrap() = true; cvar.notify_one(); return; } data[0..written.unwrap()].iter_mut().for_each(|s| *s *= BASE_VOLUME * *controls.volume()); } }, { let controls = controls.clone(); move |err| { match err { cpal::StreamError::DeviceNotAvailable => { controls.event_handler().send(PlayerEvent::DeviceChanged).unwrap(); ring_buffer_writer.cancel_write(); } cpal::StreamError::BackendSpecific { err } => { { ::core::panicking::panic_fmt(format_args!("Unknown error occurred during playback: {0}", err)); }; } } } }, None); let stream = stream.context("Could not build the stream.")?; stream.play()?; let sample_rate = config.sample_rate.0; Ok(CpalOutput { stream, spec, duration, ring_buffer_writer: rb_clone.0, ring_buffer_reader: rb_clone.1, sample_buffer, resampler, is_stream_done, normalizer: Normalizer::new(spec.channels.count(), sample_rate), controls, }) } fn get_config(spec: SignalSpec) -> anyhow::Result<(Device, StreamConfig, usize)> { let host = cpal::default_host(); let device = host.default_output_device().context("Failed to get default output device.")?; let default_output_config = device.default_output_config().context("Failed to get default output config.")?; let channels = spec.channels.count(); let default_ring_buf_size = ((200 * spec.rate) / 1000) * channels as u32; let ring_buffer_size: usize = match default_output_config.buffer_size() { cpal::SupportedBufferSize::Range { min, max: _ } => { if min <= &default_ring_buf_size { default_ring_buf_size } else { *min + 10000 } } cpal::SupportedBufferSize::Unknown => default_ring_buf_size, } as usize; let config; #[cfg(not(target_os = "windows"))] { config = cpal::StreamConfig { channels: channels as cpal::ChannelCount, sample_rate: cpal::SampleRate(spec.rate), buffer_size: cpal::BufferSize::Default, }; } Ok((device, config, ring_buffer_size)) } /// Write the `AudioBufferRef` to the buffers. pub fn write(&mut self, decoded: AudioBufferRef) { if decoded.frames() == 0 { return; } let mut samples = if let Some(resampler) = &mut self.resampler { resampler.resample(decoded).unwrap_or(&[]) } else { self.sample_buffer.copy_interleaved_ref(decoded); self.sample_buffer.samples() }; if self.controls.is_normalizing() { if let Some(normalized) = self.normalizer.normalize(samples) { samples = normalized; } } while let Some(written) = self.ring_buffer_writer.write(samples) { samples = &samples[written..]; } } /// Clean up after playback is done. pub fn flush(&mut self) { if let Some(resampler) = &mut self.resampler { let mut remaining_samples = resampler.flush().unwrap_or_default(); while let Some(written) = self.ring_buffer_writer.write(remaining_samples) { remaining_samples = &remaining_samples[written..]; } } let (mutex, cvar) = &*self.is_stream_done; let _lock = cvar.wait(mutex.lock().unwrap()); self.stream.pause().unwrap(); } } unsafe impl Send for CpalOutput {} } pub mod decoder { use std::{ sync::{atomic::AtomicBool, Arc}, thread::{self, JoinHandle}, }; use anyhow::{anyhow, Context}; use cpal::traits::StreamTrait; use crossbeam::channel::Receiver; use lazy_static::lazy_static; use symphonia::{ core::{ audio::{AsAudioBufferRef, AudioBuffer}, formats::{FormatOptions, FormatReader, SeekMode, SeekTo}, io::{MediaSource, MediaSourceStream}, meta::MetadataOptions, probe::Hint, units::{Time, TimeBase}, }, default::{self, register_enabled_codecs}, }; use symphonia_core::codecs::CodecRegistry; use crate::{ audio::opus::OpusDecoder, media_controllers, utils::{ callback_stream::update_callback_stream, playback_state_stream::update_playback_state_stream, progress_state_stream::*, types::*, }, }; use super::{controls::*, cpal_output::CpalOutput}; #[allow(missing_copy_implementations)] #[allow(non_camel_case_types)] #[allow(dead_code)] struct CODEC_REGISTRY { __private_field: (), } #[doc(hidden)] static CODEC_REGISTRY: CODEC_REGISTRY = CODEC_REGISTRY { __private_field: () }; impl ::lazy_static::__Deref for CODEC_REGISTRY { type Target = CodecRegistry; fn deref(&self) -> &CodecRegistry { #[inline(always)] fn __static_ref_initialize() -> CodecRegistry { { let mut registry = CodecRegistry::new(); register_enabled_codecs(&mut registry); registry.register_all::(); registry } } #[inline(always)] fn __stability() -> &'static CodecRegistry { static LAZY: ::lazy_static::lazy::Lazy = ::lazy_static::lazy::Lazy::INIT; LAZY.get(__static_ref_initialize) } __stability() } } impl ::lazy_static::LazyStatic for CODEC_REGISTRY { fn initialize(lazy: &Self) { let _ = &**lazy; } } pub struct Decoder { thread_killer: Receiver, controls: Controls, state: DecoderState, cpal_output: Option, playback: Option, preload_playback: Option<(Playback, CpalOutput)>, /// The `JoinHandle` for the thread that preloads a file. preload_thread: Option>>, } impl Decoder { /// Creates a new decoder. pub fn new(controls: Controls) -> Self { let thread_killer = THREAD_KILLER.get().unwrap().read().unwrap().1.clone(); Decoder { thread_killer, controls, state: DecoderState::Idle, cpal_output: None, playback: None, preload_playback: None, preload_thread: None, } } /// Starts decoding in an infinite loop. /// Listens for any incoming `ThreadMessage`s. /// /// If playing, then the decoder decodes packets /// until the file is done playing. /// /// If stopped, the decoder goes into an idle state /// where it waits for a message to come. pub fn start(mut self) { loop { if self.poll_preload_thread().is_err() { update_callback_stream(Callback::DecodeError); } match self.listen_for_message() { Ok(should_break) => { if should_break { break; } } Err(_) => { update_callback_stream(Callback::DecodeError); } } match self.do_playback() { Ok(playback_complete) => { if playback_complete { self.state = DecoderState::Idle; self.finish_playback(); } } Err(_) => { update_callback_stream(Callback::DecodeError); } } } } /// Listens for any incoming messages. /// /// Blocks if the `self.state` is `Idle` or `Paused`. /// /// Returns true if this thread should be stopped. /// Returns false otherwise. fn listen_for_message(&mut self) -> anyhow::Result { if self.thread_killer.try_recv().is_ok() { return Ok(true); } let recv: Option = if self.state.is_idle() || self.state.is_paused() { self.controls.event_handler().recv().ok() } else { self.controls.event_handler().try_recv().ok() }; match recv { None => (), Some(message) => match message { PlayerEvent::Open(source, buffer_signal) => { self.cpal_output = None; self.playback = Some(Self::open(source, buffer_signal)?); } PlayerEvent::Play => { self.state = DecoderState::Playing; #[cfg(not(target_os = "windows"))] if let Some(cpal_output) = &self.cpal_output { cpal_output.stream.play()?; } } PlayerEvent::Pause => { self.state = DecoderState::Paused; #[cfg(not(target_os = "windows"))] if let Some(cpal_output) = &self.cpal_output { cpal_output.stream.pause()?; } } PlayerEvent::Stop => { self.state = DecoderState::Idle; self.cpal_output = None; self.playback = None; } PlayerEvent::DeviceChanged => { self.cpal_output = None; crate::Player::internal_pause(&self.controls); if self.preload_playback.is_some() { let (playback, cpal_output) = self.preload_playback.take().unwrap(); let buffer_signal = playback.buffer_signal.clone(); self.preload_playback.replace((playback, CpalOutput::new(self.controls.clone(), buffer_signal, cpal_output.spec, cpal_output.duration)?)); } } PlayerEvent::Preload(source, buffer_signal) => { self.preload_playback = None; self.controls.set_is_file_preloaded(false); let handle = self.preload(source, buffer_signal); self.preload_thread = Some(handle); } PlayerEvent::PlayPreload => { if self.preload_playback.is_none() { return Ok(false); } let (playback, cpal_output) = self.preload_playback.take().unwrap(); self.playback = Some(playback); self.cpal_output = Some(cpal_output); self.controls.set_is_file_preloaded(false); crate::Player::internal_play(&self.controls); } }, } Ok(false) } /// Decodes a packet and writes to `cpal_output`. /// /// Returns `true` when the playback is complete. /// Returns `false` otherwise. fn do_playback(&mut self) -> anyhow::Result { if self.playback.is_none() || self.state.is_idle() || self.state.is_paused() { return Ok(false); } let playback = self.playback.as_mut().unwrap(); if let Some(preload) = playback.preload.take() { if self.cpal_output.is_none() { let spec = *preload.spec(); let duration = preload.capacity() as u64; self.cpal_output.replace(CpalOutput::new(self.controls.clone(), playback.buffer_signal.clone(), spec, duration)?); } let buffer_ref = preload.as_audio_buffer_ref(); self.cpal_output.as_mut().unwrap().write(buffer_ref); return Ok(false); } if let Some(seek_ts) = *self.controls.seek_ts() { let seek_to = SeekTo::Time { time: Time::from(seek_ts), track_id: Some(playback.track_id), }; playback.reader.seek(SeekMode::Coarse, seek_to)?; } if self.controls.seek_ts().is_some() { self.controls.set_seek_ts(None); playback.decoder.reset(); if let Some(cpal_output) = self.cpal_output.as_ref() { cpal_output.ring_buffer_reader.skip_all(); } return Ok(false); } let packet = match playback.reader.next_packet() { Ok(packet) => packet, Err(_) => { if self.controls.is_looping() { self.controls.set_seek_ts(Some(0)); crate::utils::callback_stream::update_callback_stream(Callback::PlaybackLooped); return Ok(false); } return Ok(true); } }; if packet.track_id() != playback.track_id { return Ok(false); } let decoded = playback.decoder.decode(&packet).context("Could not decode audio packet.")?; let position = if let Some(timebase) = playback.timebase { timebase.calc_time(packet.ts()).seconds } else { 0 }; let progress = ProgressState { position, duration: playback.duration }; Self::notify_media_controllers_with_progress(&self.controls.progress(), &progress); update_progress_state_stream(progress); self.controls.set_progress(progress); if self.cpal_output.is_none() { let spec = *decoded.spec(); let duration = decoded.capacity() as u64; self.cpal_output.replace(CpalOutput::new(self.controls.clone(), playback.buffer_signal.clone(), spec, duration)?); } self.cpal_output.as_mut().unwrap().write(decoded); Ok(false) } /// Called when the file is finished playing. /// /// Flushes `cpal_output` and sends a `Done` message to Dart. fn finish_playback(&mut self) { if let Some(cpal_output) = self.cpal_output.as_mut() { cpal_output.flush(); } update_playback_state_stream(PlaybackState::Done); let progress_state = ProgressState { position: 0, duration: 0 }; update_progress_state_stream(progress_state); self.controls.set_progress(progress_state); self.controls.set_is_playing(false); self.controls.set_is_stopped(true); crate::media_controllers::set_playback_state(PlaybackState::Done); } /// Opens the given source for playback. Returns a `Playback` /// for the source. fn open(source: Box, buffer_signal: Arc) -> anyhow::Result { let mss = MediaSourceStream::new(source, Default::default()); let format_options = FormatOptions { enable_gapless: true, ..Default::default() }; let metadata_options: MetadataOptions = Default::default(); let probed = default::get_probe().format(&Hint::new(), mss, &format_options, &metadata_options).context("Failed to create format reader.")?; let reader = probed.format; let track = reader.default_track().context("Cannot start playback. There are no tracks present in the file.")?; let track_id = track.id; let decoder = CODEC_REGISTRY.make(&track.codec_params, &Default::default())?; let timebase = track.codec_params.time_base.or_else(|| { track.codec_params.sample_rate.map(|sample_rate| TimeBase::new(1, sample_rate)) }); let ts = track.codec_params.n_frames.map(|frames| track.codec_params.start_ts + frames); let duration = match (timebase, ts) { (Some(timebase), Some(ts)) => timebase.calc_time(ts).seconds, _ => 0, }; Ok(Playback { reader, decoder, track_id, timebase, duration, buffer_signal, preload: None, }) } /// Spawns a thread that decodes the first packet of the source. /// /// Returns a preloaded `Playback` and `CpalOutput` when complete. fn preload(&self, source: Box, buffer_signal: Arc) -> JoinHandle> { let controls = self.controls.clone(); thread::spawn(move || { let mut playback = Self::open(source, buffer_signal.clone())?; let packet = playback.reader.next_packet()?; let buf_ref = playback.decoder.decode(&packet)?; let spec = *buf_ref.spec(); let duration = buf_ref.capacity() as u64; let mut buf = AudioBuffer::new(duration, spec); buf_ref.convert(&mut buf); playback.preload = Some(buf); let cpal_output = CpalOutput::new(controls, buffer_signal, spec, duration)?; #[cfg(not(target_os = "windows"))] cpal_output.stream.pause()?; Ok((playback, cpal_output)) }) } /// Polls the `preload_thread`. /// /// If it is finished, the preloaded file /// is then placed in `preload_playback`. fn poll_preload_thread(&mut self) -> anyhow::Result<()> { if self.preload_thread.is_none() || !self.preload_thread.as_ref().unwrap().is_finished() { return Ok(()); } let handle = self.preload_thread.take().unwrap(); let result = handle.join().unwrap_or(Err(::anyhow::__private::must_use({ let error = ::anyhow::__private::format_err(format_args!("Could not join preload thread.")); error })))?; self.preload_playback.replace(result); self.controls.set_is_file_preloaded(true); Ok(()) } fn notify_media_controllers_with_progress(curr_progress: &ProgressState, new_progress: &ProgressState) { if curr_progress.duration == 0 { update_callback_stream(Callback::DurationCalculated); media_controllers::set_duration(new_progress.duration); } if curr_progress.position < new_progress.position { media_controllers::set_position(new_progress.position); } } } enum DecoderState { Playing, Paused, Idle, } impl DecoderState { fn is_idle(&self) -> bool { if let DecoderState::Idle = self { return true; } false } fn is_paused(&self) -> bool { if let DecoderState::Paused = self { return true; } false } } /// Holds the items related to playback. /// /// Ex: The Symphonia decoder, timebase, duration. struct Playback { reader: Box, track_id: u32, decoder: Box, timebase: Option, duration: u64, buffer_signal: Arc, /// A buffer of already decoded samples. preload: Option>, } } mod dsp { pub mod normalizer { use ebur128::*; /// The target LUFS value. const NORMALIZE_TO: f64 = -14.0; const LOWER_THRESHOLD: f32 = 0.2; pub struct Normalizer { ebur128: EbuR128, buffer: Vec, /// True if the input samples are loud enough to start being normalized. /// This prevents normalizing parts of a song that the artist intented to be quiet. passed_lower_threshold: bool, } impl Normalizer { pub fn new(channels: usize, sample_rate: u32) -> Self { let ebur128 = EbuR128::new(channels as u32, sample_rate, Mode::I.union(Mode::M)).unwrap(); Normalizer { ebur128, buffer: Vec::new(), passed_lower_threshold: false, } } pub fn normalize(&mut self, input: &[f32]) -> Option<&[f32]> { if !input.iter().any(|x| *x != 0.0) { return None; } if !self.passed_lower_threshold { let samples_passing_threshold = input[0..3].iter().find(|e| **e >= LOWER_THRESHOLD); self.passed_lower_threshold = samples_passing_threshold.is_some(); return None; } let _ = self.ebur128.add_frames_f32(input); let global_loudness = self.ebur128.loudness_global().unwrap(); let gain = if global_loudness.is_finite() { calc_gain(global_loudness) } else { let loudness = self.ebur128.loudness_momentary().unwrap(); calc_gain(loudness) }; let gain = gain.clamp(0.0, 1.2); self.buffer.clear(); self.buffer.extend_from_slice(input); self.buffer.iter_mut().for_each(|sample| *sample *= gain); Some(&self.buffer) } } fn calc_gain(loudness: f64) -> f32 { let gain_db = NORMALIZE_TO - loudness; 10.0_f32.powf(gain_db as f32 / 20.0) } } pub mod resampler { use symphonia::core::audio::{ AudioBuffer, AudioBufferRef, Signal, SignalSpec, }; use symphonia::core::conv::{FromSample, IntoSample}; use symphonia::core::sample::Sample; pub struct Resampler { resampler: rubato::FftFixedIn, input: Vec>, output: Vec>, interleaved: Vec, duration: usize, } impl Resampler where T: Sample + FromSample + IntoSample { fn resample_inner(&mut self) -> &[T] { { let mut input: arrayvec::ArrayVec<&[f32], 32> = Default::default(); for channel in self.input.iter() { input.push(&channel[..self.duration]); } rubato::Resampler::process_into_buffer(&mut self.resampler, &input, &mut self.output, None).unwrap(); } for channel in self.input.iter_mut() { channel.drain(0..self.duration); } let num_channels = self.output.len(); self.interleaved.resize(num_channels * self.output[0].len(), T::MID); for (i, frame) in self.interleaved.chunks_exact_mut(num_channels).enumerate() { for (ch, s) in frame.iter_mut().enumerate() { *s = self.output[ch][i].into_sample(); } } &self.interleaved } } impl Resampler where T: Sample + FromSample + IntoSample { pub fn new(spec: SignalSpec, to_sample_rate: usize, duration: u64) -> Self { let duration = duration as usize; let num_channels = spec.channels.count(); let resampler = rubato::FftFixedIn::::new(spec.rate as usize, to_sample_rate, duration, 2, num_channels).unwrap(); let output = rubato::Resampler::output_buffer_allocate(&resampler); let input = ::alloc::vec::from_elem(Vec::with_capacity(duration), num_channels); Self { resampler, input, output, duration, interleaved: Default::default(), } } /// Resamples a planar/non-interleaved input. /// /// Returns the resampled samples in an interleaved format. pub fn resample(&mut self, input: AudioBufferRef<'_>) -> Option<&[T]> { convert_samples_any(&input, &mut self.input); if self.input[0].len() < self.duration { return None; } Some(self.resample_inner()) } /// Resample any remaining samples in the resample buffer. pub fn flush(&mut self) -> Option<&[T]> { let len = self.input[0].len(); if len == 0 { return None; } let partial_len = len % self.duration; if partial_len != 0 { for channel in self.input.iter_mut() { channel.resize(len + (self.duration - partial_len), f32::MID); } } Some(self.resample_inner()) } } fn convert_samples_any(input: &AudioBufferRef<'_>, output: &mut [Vec]) { match input { AudioBufferRef::U8(input) => convert_samples(input, output), AudioBufferRef::U16(input) => convert_samples(input, output), AudioBufferRef::U24(input) => convert_samples(input, output), AudioBufferRef::U32(input) => convert_samples(input, output), AudioBufferRef::S8(input) => convert_samples(input, output), AudioBufferRef::S16(input) => convert_samples(input, output), AudioBufferRef::S24(input) => convert_samples(input, output), AudioBufferRef::S32(input) => convert_samples(input, output), AudioBufferRef::F32(input) => convert_samples(input, output), AudioBufferRef::F64(input) => convert_samples(input, output), } } fn convert_samples(input: &AudioBuffer, output: &mut [Vec]) where S: Sample + IntoSample { for (c, dst) in output.iter_mut().enumerate() { let src = input.chan(c); dst.extend(src.iter().map(|&s| s.into_sample())); } } } } mod opus { use audiopus::{ coder::{Decoder as AudiopusDecoder, GenericCtl}, Channels, Error as OpusError, ErrorCode, SampleRate, }; use symphonia_core::{ audio::{ AsAudioBufferRef, AudioBuffer, AudioBufferRef, Layout, Signal, SignalSpec, }, codecs::{ CodecDescriptor, CodecParameters, Decoder, DecoderOptions, FinalizeResult, CODEC_TYPE_OPUS, }, errors::{decode_error, Result as SymphResult}, formats::Packet, }; const SAMPLE_RATE: SampleRate = SampleRate::Hz48000; const SAMPLE_RATE_RAW: usize = 48_000; /// This is equally the number of stereo (joint) samples in an audio frame. const MONO_FRAME_SIZE: usize = SAMPLE_RATE_RAW / 1000 * 60; /// Number of individual samples in one complete frame of stereo audio. const STEREO_FRAME_SIZE: usize = 2 * MONO_FRAME_SIZE; /// Opus decoder for symphonia, based on libopus v1.3 (via [`audiopus`]). pub struct OpusDecoder { inner: AudiopusDecoder, params: CodecParameters, buf: AudioBuffer, rawbuf: Vec, } /// # SAFETY /// The underlying Opus decoder (currently) requires only a `&self` parameter /// to decode given packets, which is likely a mistaken decision. /// /// This struct makes stronger assumptions and only touches FFI decoder state with a /// `&mut self`, preventing data races via `&OpusDecoder` as required by `impl Sync`. /// No access to other internal state relies on unsafety or crosses FFI. unsafe impl Sync for OpusDecoder { } impl OpusDecoder { fn decode_inner(&mut self, packet: &Packet) -> SymphResult<()> { let s_ct = loop { let pkt = if packet.buf().is_empty() { None } else if let Ok(checked_pkt) = packet.buf().try_into() { Some(checked_pkt) } else { return decode_error("Opus packet was too large (greater than i32::MAX bytes)."); }; let out_space = (&mut self.rawbuf[..]).try_into().expect("The following logic expands this buffer safely below i32::MAX, and we throw our own error."); match self.inner.decode_float(pkt, out_space, false) { Ok(v) => break v, Err(OpusError::Opus(ErrorCode::BufferTooSmall)) => { let new_size = (self.rawbuf.len() * 2).min(std::i32::MAX as usize); if new_size == self.rawbuf.len() { return decode_error("Opus frame too big: cannot expand opus frame decode buffer any further."); } self.rawbuf.resize(new_size, 0.0); self.buf = AudioBuffer::new(self.rawbuf.len() as u64 / 2, SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo)); } Err(_) => { return decode_error("Opus decode error"); } } }; self.buf.clear(); self.buf.render_reserved(Some(s_ct)); for ch in 0..2 { let iter = self.rawbuf.chunks_exact(2).map(|chunk| chunk[ch]); for (tgt, src) in self.buf.chan_mut(ch).iter_mut().zip(iter) { *tgt = src; } } Ok(()) } } impl Decoder for OpusDecoder { fn try_new(params: &CodecParameters, _options: &DecoderOptions) -> SymphResult { let inner = AudiopusDecoder::new(SAMPLE_RATE, Channels::Stereo).unwrap(); let mut params = params.clone(); params.with_sample_rate(SAMPLE_RATE_RAW as u32); Ok(Self { inner, params, buf: AudioBuffer::new(MONO_FRAME_SIZE as u64, SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo)), rawbuf: ::alloc::vec::from_elem(0.0f32, STEREO_FRAME_SIZE), }) } fn supported_codecs() -> &'static [CodecDescriptor] { &[CodecDescriptor { codec: CODEC_TYPE_OPUS, short_name: "opus", long_name: "libopus (1.3+, audiopus)", inst_func: |params, opt| Ok(Box::new(Self::try_new(¶ms, &opt)?)), }] } fn codec_params(&self) -> &CodecParameters { &self.params } fn decode(&mut self, packet: &Packet) -> SymphResult> { if let Err(e) = self.decode_inner(packet) { self.buf.clear(); Err(e) } else { Ok(self.buf.as_audio_buffer_ref()) } } fn reset(&mut self) { _ = self.inner.reset_state(); } fn finalize(&mut self) -> FinalizeResult { FinalizeResult::default() } fn last_decoded(&self) -> AudioBufferRef { self.buf.as_audio_buffer_ref() } } } pub mod sources { pub mod hls { use std::io::{Read, Seek}; use std::ops::Range; use std::sync::atomic::AtomicBool; use std::sync::mpsc::{channel, Sender}; use std::sync::Arc; use std::thread; use anyhow::Context; use rangemap::RangeSet; use reqwest::blocking::Client; use symphonia::core::io::MediaSource; use crate::utils::callback_stream::update_callback_stream; use crate::utils::types::Callback; use super::{streamable::*, Receiver}; pub struct HlsStream { /// A list of parts with their size and URL. urls: Vec<(Range, String)>, buffer: Vec, read_position: usize, downloaded: RangeSet, requested: RangeSet, receivers: Vec, buffer_signal: Arc, } impl HlsStream { pub fn new(url: String, buffer_signal: Arc) -> anyhow::Result { let mut urls = Vec::new(); let mut total_size = 0; let mut buffer = Vec::new(); let mut downloaded = RangeSet::new(); let mut requested = RangeSet::new(); let file = Client::new().get(url).send()?.text()?; for line in file.lines() { if !line.contains("http") { continue; } let res = Client::new().head(line).send()?.error_for_status()?; let header = res.headers().get("Content-Length"); match header { Some(content_length) => { let size = content_length.to_str()?.parse()?; urls.push((total_size..total_size + size + 1, line.to_string())); total_size += size; buffer.extend_from_slice(&::alloc::vec::from_elem(0, size)); } None => { let response = Client::new().get(line).header("Range", "bytes=0-").send().context("Could not download part for playback.")?; let mut bytes = response.bytes()?.to_vec(); let size = bytes.len(); buffer.append(&mut bytes); downloaded.insert(total_size..total_size + size); requested.insert(total_size..total_size + size); total_size += size; } } } Ok(HlsStream { urls, buffer, read_position: 0, downloaded, requested, receivers: Vec::new(), buffer_signal, }) } } impl Streamable for HlsStream { fn read_chunk(tx: Sender<(usize, Vec)>, url: String, start: usize, _: usize) -> anyhow::Result<()> { let chunk = Client::new().get(url).send()?.error_for_status()?.bytes()?.to_vec(); let _ = tx.send((start, chunk)); Ok(()) } fn try_write_chunk(&mut self, should_buffer: bool) { let mut completed_downloads = Vec::new(); for Receiver { id, receiver } in &self.receivers { let result = if self.downloaded.is_empty() || should_buffer { receiver.recv().ok() } else { receiver.try_recv().ok() }; match result { None => (), Some((position, chunk)) => { let end = position + chunk.len(); if position != end { self.buffer[position..end].copy_from_slice(chunk.as_slice()); self.downloaded.insert(position..end); } completed_downloads.push(*id); } } } self.receivers.retain(|receiver| !completed_downloads.contains(&receiver.id)); } fn should_get_chunk(&self) -> (bool, usize) { let closest_range = self.downloaded.get(&self.read_position); if closest_range.is_none() { return (true, self.read_position); } let closest_range = closest_range.unwrap(); let is_already_downloading = self.requested.contains(&(self.read_position + CHUNK_SIZE)); let prefetch_pos = self.read_position + (CHUNK_SIZE * 2); let should_get_chunk = prefetch_pos >= closest_range.end && !is_already_downloading && closest_range.end != self.buffer.len(); (should_get_chunk, closest_range.end) } } impl Read for HlsStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { if self.read_position >= self.buffer.len() { return Ok(0); } let read_max = (self.read_position + buf.len()).min(self.buffer.len()); let (should_get_chunk, chunk_write_pos) = self.should_get_chunk(); if should_get_chunk { let part = self.urls.iter().find(|(range, _)| range.contains(&chunk_write_pos)); if let Some((range, url)) = part { self.requested.insert(range.clone()); let url = url.clone(); let start = range.start; let file_size = self.buffer.len(); let (tx, receiver) = channel(); let id = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(); self.receivers.push(Receiver { id, receiver }); thread::spawn(move || { let result = Self::read_chunk(tx, url, start, file_size); if result.is_err() { update_callback_stream(Callback::NetworkStreamError) } }); let index = self.urls.iter().position(|x| x == part.unwrap()).unwrap(); self.urls.remove(index); } } let should_buffer = !self.downloaded.contains(&self.read_position); self.buffer_signal.store(should_buffer, std::sync::atomic::Ordering::SeqCst); self.try_write_chunk(should_buffer); let bytes = &self.buffer[self.read_position..read_max]; buf[0..bytes.len()].copy_from_slice(bytes); self.read_position += bytes.len(); Ok(bytes.len()) } } impl Seek for HlsStream { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { let seek_position: usize = match pos { std::io::SeekFrom::Start(pos) => pos as usize, std::io::SeekFrom::Current(pos) => { let pos = self.read_position as i64 + pos; pos.try_into().map_err(|_| { std::io::Error::new(std::io::ErrorKind::InvalidInput, { let res = ::alloc::fmt::format(format_args!("Invalid seek: {0}", pos)); res }) })? } std::io::SeekFrom::End(pos) => { let pos = self.buffer.len() as i64 + pos; pos.try_into().map_err(|_| { std::io::Error::new(std::io::ErrorKind::InvalidInput, { let res = ::alloc::fmt::format(format_args!("Invalid seek: {0}", pos)); res }) })? } }; if seek_position > self.buffer.len() { return Ok(self.read_position as u64); } self.read_position = seek_position; Ok(seek_position as u64) } } unsafe impl Send for HlsStream {} unsafe impl Sync for HlsStream {} impl MediaSource for HlsStream { fn is_seekable(&self) -> bool { true } fn byte_len(&self) -> Option { Some(self.buffer.len() as u64) } } } pub mod http { use std::io::{Read, Seek}; use std::sync::atomic::AtomicBool; use std::sync::mpsc::{channel, Sender}; use std::sync::Arc; use std::thread; use anyhow::Context; use rangemap::RangeSet; use reqwest::blocking::Client; use symphonia::core::io::MediaSource; use crate::utils::callback_stream::update_callback_stream; use crate::utils::types::Callback; use super::{streamable::*, Receiver}; pub struct HttpStream { url: String, buffer: Vec, read_position: usize, downloaded: RangeSet, requested: RangeSet, receivers: Vec, buffer_signal: Arc, } impl HttpStream { pub fn new(url: String, buffer_signal: Arc) -> anyhow::Result { let res = Client::new().head(&url).send()?.error_for_status()?; let header = res.headers().get("Content-Length"); let buffer; let mut downloaded = RangeSet::new(); let mut requested = RangeSet::new(); match header { Some(content_length) => { buffer = ::alloc::vec::from_elem(0, content_length.to_str()?.parse()?); } None => { let response = Client::new().get(&url).header("Range", "bytes=0-").send().context("Could not download file for playback.")?; let bytes = response.bytes()?.to_vec(); buffer = bytes; downloaded.insert(0..buffer.len()); requested.insert(0..buffer.len()); } } Ok(HttpStream { url, buffer, read_position: 0, downloaded, requested, receivers: Vec::new(), buffer_signal, }) } } impl Streamable for HttpStream { /// Gets the next chunk in the sequence. /// /// Returns the received bytes by sending them via `tx`. fn read_chunk(tx: Sender<(usize, Vec)>, url: String, start: usize, file_size: usize) -> anyhow::Result<()> { let end = (start + CHUNK_SIZE).min(file_size) - 1; let chunk = Client::new().get(url).header("Range", { let res = ::alloc::fmt::format(format_args!("bytes={0}-{1}", start, end)); res }).send()?.error_for_status()?.bytes()?.to_vec(); let _ = tx.send((start, chunk)); Ok(()) } /// Polls all receivers. /// /// If there is data to receive, then write it to the buffer. /// /// Changes made are commited to `downloaded`. fn try_write_chunk(&mut self, should_buffer: bool) { let mut completed_downloads = Vec::new(); for Receiver { id, receiver } in &self.receivers { let result = if self.downloaded.is_empty() || should_buffer { receiver.recv().ok() } else { receiver.try_recv().ok() }; match result { None => (), Some((position, chunk)) => { let end = (position + chunk.len()).min(self.buffer.len()); if position != end { self.buffer[position..end].copy_from_slice(chunk.as_slice()); self.downloaded.insert(position..end); } completed_downloads.push(*id); } } } self.receivers.retain(|receiver| !completed_downloads.contains(&receiver.id)); } /// Determines if a chunk should be downloaded by getting /// the downloaded range that contains `self.read_position`. /// /// Returns `true` and the start index of the chunk /// if one should be downloaded. fn should_get_chunk(&self) -> (bool, usize) { let closest_range = self.downloaded.get(&self.read_position); if closest_range.is_none() { return (true, self.read_position); } let closest_range = closest_range.unwrap(); let is_already_downloading = self.requested.contains(&(self.read_position + CHUNK_SIZE)); let prefetch_pos = self.read_position + (CHUNK_SIZE * 2); let should_get_chunk = prefetch_pos >= closest_range.end && !is_already_downloading && closest_range.end != self.buffer.len(); (should_get_chunk, closest_range.end) } } impl Read for HttpStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { if self.read_position >= self.buffer.len() { return Ok(0); } let read_max = (self.read_position + buf.len()).min(self.buffer.len()); let (should_get_chunk, chunk_write_pos) = self.should_get_chunk(); if should_get_chunk { self.requested.insert(chunk_write_pos..chunk_write_pos + CHUNK_SIZE + 1); let url = self.url.clone(); let file_size = self.buffer.len(); let (tx, receiver) = channel(); let id = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(); self.receivers.push(Receiver { id, receiver }); thread::spawn(move || { let result = Self::read_chunk(tx, url, chunk_write_pos, file_size); if result.is_err() { update_callback_stream(Callback::NetworkStreamError) } }); } let should_buffer = !self.downloaded.contains(&self.read_position); self.buffer_signal.store(should_buffer, std::sync::atomic::Ordering::SeqCst); self.try_write_chunk(should_buffer); let bytes = &self.buffer[self.read_position..read_max]; buf[0..bytes.len()].copy_from_slice(bytes); self.read_position += bytes.len(); Ok(bytes.len()) } } impl Seek for HttpStream { fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { let seek_position: usize = match pos { std::io::SeekFrom::Start(pos) => pos as usize, std::io::SeekFrom::Current(pos) => { let pos = self.read_position as i64 + pos; pos.try_into().map_err(|_| { std::io::Error::new(std::io::ErrorKind::InvalidInput, { let res = ::alloc::fmt::format(format_args!("Invalid seek: {0}", pos)); res }) })? } std::io::SeekFrom::End(pos) => { let pos = self.buffer.len() as i64 + pos; pos.try_into().map_err(|_| { std::io::Error::new(std::io::ErrorKind::InvalidInput, { let res = ::alloc::fmt::format(format_args!("Invalid seek: {0}", pos)); res }) })? } }; if seek_position > self.buffer.len() { return Ok(self.read_position as u64); } self.read_position = seek_position; Ok(seek_position as u64) } } unsafe impl Send for HttpStream {} unsafe impl Sync for HttpStream {} impl MediaSource for HttpStream { fn is_seekable(&self) -> bool { true } fn byte_len(&self) -> Option { Some(self.buffer.len() as u64) } } } pub mod streamable { use std::{ io::{Read, Seek}, sync::mpsc::Sender, }; use symphonia::core::io::MediaSource; pub const CHUNK_SIZE: usize = 1024 * 256; pub trait Streamable: Read + Seek + Send + Sync + MediaSource { fn read_chunk(tx: Sender<(usize, Vec)>, url: String, start: usize, file_size: usize) -> anyhow::Result<()>; fn try_write_chunk(&mut self, should_buffer: bool); fn should_get_chunk(&self) -> (bool, usize); } } /// A type that holds an ID and a `std::sync::mpsc::Receiver`. /// Used for multithreaded download of audio data. struct Receiver { id: u128, receiver: std::sync::mpsc::Receiver<(usize, Vec)>, } } } mod bridge_generated { #![allow(non_camel_case_types, unused, clippy :: redundant_closure, clippy :: useless_conversion, clippy :: unit_arg, clippy :: double_parens, non_snake_case, clippy :: too_many_arguments)] use crate::*; use core::panic::UnwindSafe; use flutter_rust_bridge::rust2dart::IntoIntoDart; use flutter_rust_bridge::*; use std::ffi::c_void; use std::sync::Arc; use crate::media_controllers::types::MediaControlAction; use crate::media_controllers::types::Metadata; use crate::utils::types::Callback; use crate::utils::types::PlaybackState; use crate::utils::types::ProgressState; use crate::Player; fn wire_new__static_method__Player_impl(port_: MessagePort, actions: impl Wire2Api> + UnwindSafe, dbus_name: impl Wire2Api + UnwindSafe, hwnd: impl Wire2Api> + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, Player>(WrapInfo { debug_name: "new__static_method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_actions = actions.wire2api(); let api_dbus_name = dbus_name.wire2api(); let api_hwnd = hwnd.wire2api(); move |task_callback| Ok(Player::new(api_actions, api_dbus_name, api_hwnd)) }) } fn wire_dispose__static_method__Player_impl(port_: MessagePort) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "dispose__static_method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || move |task_callback| Ok(Player::dispose())) } fn wire_playback_state_stream__static_method__Player_impl(port_: MessagePort) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "playback_state_stream__static_method__Player", port: Some(port_), mode: FfiCallMode::Stream, }, move || { move |task_callback| { Ok(Player::playback_state_stream(task_callback.stream_sink::<_, PlaybackState>())) } }) } fn wire_progress_state_stream__static_method__Player_impl(port_: MessagePort) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "progress_state_stream__static_method__Player", port: Some(port_), mode: FfiCallMode::Stream, }, move || { move |task_callback| { Ok(Player::progress_state_stream(task_callback.stream_sink::<_, ProgressState>())) } }) } fn wire_callback_stream__static_method__Player_impl(port_: MessagePort) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "callback_stream__static_method__Player", port: Some(port_), mode: FfiCallMode::Stream, }, move || { move |task_callback| { Ok(Player::callback_stream(task_callback.stream_sink::<_, Callback>())) } }) } fn wire_is_playing__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, bool>(WrapInfo { debug_name: "is_playing__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); move |task_callback| Ok(Player::is_playing(&api_that)) }) } fn wire_has_preloaded__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, bool>(WrapInfo { debug_name: "has_preloaded__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); move |task_callback| Ok(Player::has_preloaded(&api_that)) }) } fn wire_get_progress__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ProgressState>(WrapInfo { debug_name: "get_progress__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); move |task_callback| Ok(Player::get_progress(&api_that)) }) } fn wire_open__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe, path: impl Wire2Api + UnwindSafe, autoplay: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "open__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); let api_path = path.wire2api(); let api_autoplay = autoplay.wire2api(); move |task_callback| Player::open(&api_that, api_path, api_autoplay) }) } fn wire_preload__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe, path: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "preload__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); let api_path = path.wire2api(); move |task_callback| Player::preload(&api_that, api_path) }) } fn wire_play_preload__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "play_preload__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); move |task_callback| Player::play_preload(&api_that) }) } fn wire_play__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "play__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); move |task_callback| Ok(Player::play(&api_that)) }) } fn wire_pause__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "pause__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); move |task_callback| Ok(Player::pause(&api_that)) }) } fn wire_stop__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "stop__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); move |task_callback| Ok(Player::stop(&api_that)) }) } fn wire_loop_playback__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe, should_loop: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "loop_playback__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); let api_should_loop = should_loop.wire2api(); move |task_callback| Ok(Player::loop_playback(&api_that, api_should_loop)) }) } fn wire_set_volume__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe, volume: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "set_volume__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); let api_volume = volume.wire2api(); move |task_callback| Ok(Player::set_volume(&api_that, api_volume)) }) } fn wire_seek__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe, seconds: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "seek__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); let api_seconds = seconds.wire2api(); move |task_callback| Ok(Player::seek(&api_that, api_seconds)) }) } fn wire_set_metadata__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe, metadata: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "set_metadata__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); let api_metadata = metadata.wire2api(); move |task_callback| Ok(Player::set_metadata(&api_that, api_metadata)) }) } fn wire_normalize_volume__method__Player_impl(port_: MessagePort, that: impl Wire2Api + UnwindSafe, should_normalize: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap::<_, _, _, ()>(WrapInfo { debug_name: "normalize_volume__method__Player", port: Some(port_), mode: FfiCallMode::Normal, }, move || { let api_that = that.wire2api(); let api_should_normalize = should_normalize.wire2api(); move |task_callback| Ok(Player::normalize_volume(&api_that, api_should_normalize)) }) } pub trait Wire2Api { fn wire2api(self) -> T; } impl Wire2Api> for *mut S where *mut S: Wire2Api { fn wire2api(self) -> Option { (!self.is_null()).then(|| self.wire2api()) } } impl Wire2Api for bool { fn wire2api(self) -> bool { self } } impl Wire2Api for f32 { fn wire2api(self) -> f32 { self } } impl Wire2Api for i32 { fn wire2api(self) -> i32 { self } } impl Wire2Api for i64 { fn wire2api(self) -> i64 { self } } impl Wire2Api for i32 { fn wire2api(self) -> MediaControlAction { match self { 0 => MediaControlAction::Rewind, 1 => MediaControlAction::SkipPrev, 2 => MediaControlAction::PlayPause, 3 => MediaControlAction::SkipNext, 4 => MediaControlAction::FastForward, _ => { ::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}", format_args!("Invalid variant for MediaControlAction: {0}", self))); } } } } impl Wire2Api for u64 { fn wire2api(self) -> u64 { self } } impl Wire2Api for u8 { fn wire2api(self) -> u8 { self } } impl support::IntoDart for Callback { fn into_dart(self) -> support::DartAbi { match self { Self::MediaControlSkipPrev => 0, Self::MediaControlSkipNext => 1, Self::NetworkStreamError => 2, Self::DecodeError => 3, Self::PlaybackLooped => 4, Self::DurationCalculated => 5, }.into_dart() } } impl support::IntoDartExceptPrimitive for Callback {} impl rust2dart::IntoIntoDart for Callback { fn into_into_dart(self) -> Self { self } } impl support::IntoDart for PlaybackState { fn into_dart(self) -> support::DartAbi { match self { Self::Play => 0, Self::Pause => 1, Self::Done => 2, }.into_dart() } } impl support::IntoDartExceptPrimitive for PlaybackState {} impl rust2dart::IntoIntoDart for PlaybackState { fn into_into_dart(self) -> Self { self } } impl support::IntoDart for Player { fn into_dart(self) -> support::DartAbi { <[_]>::into_vec(#[rustc_box] ::alloc::boxed::Box::new([self.controls.into_dart()])).into_dart() } } impl support::IntoDartExceptPrimitive for Player {} impl rust2dart::IntoIntoDart for Player { fn into_into_dart(self) -> Self { self } } impl support::IntoDart for ProgressState { fn into_dart(self) -> support::DartAbi { <[_]>::into_vec(#[rustc_box] ::alloc::boxed::Box::new([self.position.into_into_dart().into_dart(), self.duration.into_into_dart().into_dart()])).into_dart() } } impl support::IntoDartExceptPrimitive for ProgressState {} impl rust2dart::IntoIntoDart for ProgressState { fn into_into_dart(self) -> Self { self } } #[allow(missing_copy_implementations)] #[allow(non_camel_case_types)] #[allow(dead_code)] pub struct FLUTTER_RUST_BRIDGE_HANDLER { __private_field: (), } #[doc(hidden)] pub static FLUTTER_RUST_BRIDGE_HANDLER: FLUTTER_RUST_BRIDGE_HANDLER = FLUTTER_RUST_BRIDGE_HANDLER { __private_field: () }; impl ::lazy_static::__Deref for FLUTTER_RUST_BRIDGE_HANDLER { type Target = support::DefaultHandler; fn deref(&self) -> &support::DefaultHandler { #[inline(always)] fn __static_ref_initialize() -> support::DefaultHandler { Default::default() } #[inline(always)] fn __stability() -> &'static support::DefaultHandler { static LAZY: ::lazy_static::lazy::Lazy = ::lazy_static::lazy::Lazy::INIT; LAZY.get(__static_ref_initialize) } __stability() } } impl ::lazy_static::LazyStatic for FLUTTER_RUST_BRIDGE_HANDLER { fn initialize(lazy: &Self) { let _ = &**lazy; } } #[cfg(not(target_family = "wasm"))] #[path = "bridge_generated.io.rs"] mod io { use super::*; #[no_mangle] pub extern "C" fn wire_new__static_method__Player(port_: i64, actions: *mut wire_list_media_control_action, dbus_name: *mut wire_uint_8_list, hwnd: *mut i64) { wire_new__static_method__Player_impl(port_, actions, dbus_name, hwnd) } #[no_mangle] pub extern "C" fn wire_dispose__static_method__Player(port_: i64) { wire_dispose__static_method__Player_impl(port_) } #[no_mangle] pub extern "C" fn wire_playback_state_stream__static_method__Player(port_: i64) { wire_playback_state_stream__static_method__Player_impl(port_) } #[no_mangle] pub extern "C" fn wire_progress_state_stream__static_method__Player(port_: i64) { wire_progress_state_stream__static_method__Player_impl(port_) } #[no_mangle] pub extern "C" fn wire_callback_stream__static_method__Player(port_: i64) { wire_callback_stream__static_method__Player_impl(port_) } #[no_mangle] pub extern "C" fn wire_is_playing__method__Player(port_: i64, that: *mut wire_Player) { wire_is_playing__method__Player_impl(port_, that) } #[no_mangle] pub extern "C" fn wire_has_preloaded__method__Player(port_: i64, that: *mut wire_Player) { wire_has_preloaded__method__Player_impl(port_, that) } #[no_mangle] pub extern "C" fn wire_get_progress__method__Player(port_: i64, that: *mut wire_Player) { wire_get_progress__method__Player_impl(port_, that) } #[no_mangle] pub extern "C" fn wire_open__method__Player(port_: i64, that: *mut wire_Player, path: *mut wire_uint_8_list, autoplay: bool) { wire_open__method__Player_impl(port_, that, path, autoplay) } #[no_mangle] pub extern "C" fn wire_preload__method__Player(port_: i64, that: *mut wire_Player, path: *mut wire_uint_8_list) { wire_preload__method__Player_impl(port_, that, path) } #[no_mangle] pub extern "C" fn wire_play_preload__method__Player(port_: i64, that: *mut wire_Player) { wire_play_preload__method__Player_impl(port_, that) } #[no_mangle] pub extern "C" fn wire_play__method__Player(port_: i64, that: *mut wire_Player) { wire_play__method__Player_impl(port_, that) } #[no_mangle] pub extern "C" fn wire_pause__method__Player(port_: i64, that: *mut wire_Player) { wire_pause__method__Player_impl(port_, that) } #[no_mangle] pub extern "C" fn wire_stop__method__Player(port_: i64, that: *mut wire_Player) { wire_stop__method__Player_impl(port_, that) } #[no_mangle] pub extern "C" fn wire_loop_playback__method__Player(port_: i64, that: *mut wire_Player, should_loop: bool) { wire_loop_playback__method__Player_impl(port_, that, should_loop) } #[no_mangle] pub extern "C" fn wire_set_volume__method__Player(port_: i64, that: *mut wire_Player, volume: f32) { wire_set_volume__method__Player_impl(port_, that, volume) } #[no_mangle] pub extern "C" fn wire_seek__method__Player(port_: i64, that: *mut wire_Player, seconds: u64) { wire_seek__method__Player_impl(port_, that, seconds) } #[no_mangle] pub extern "C" fn wire_set_metadata__method__Player(port_: i64, that: *mut wire_Player, metadata: *mut wire_Metadata) { wire_set_metadata__method__Player_impl(port_, that, metadata) } #[no_mangle] pub extern "C" fn wire_normalize_volume__method__Player(port_: i64, that: *mut wire_Player, should_normalize: bool) { wire_normalize_volume__method__Player_impl(port_, that, should_normalize) } #[no_mangle] pub extern "C" fn new_Controls() -> wire_Controls { wire_Controls::new_with_null_ptr() } #[no_mangle] pub extern "C" fn new_box_autoadd_i64_0(value: i64) -> *mut i64 { support::new_leak_box_ptr(value) } #[no_mangle] pub extern "C" fn new_box_autoadd_metadata_0() -> *mut wire_Metadata { support::new_leak_box_ptr(wire_Metadata::new_with_null_ptr()) } #[no_mangle] pub extern "C" fn new_box_autoadd_player_0() -> *mut wire_Player { support::new_leak_box_ptr(wire_Player::new_with_null_ptr()) } #[no_mangle] pub extern "C" fn new_list_media_control_action_0(len: i32) -> *mut wire_list_media_control_action { let wrap = wire_list_media_control_action { ptr: support::new_leak_vec_ptr(Default::default(), len), len, }; support::new_leak_box_ptr(wrap) } #[no_mangle] pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { let ans = wire_uint_8_list { ptr: support::new_leak_vec_ptr(Default::default(), len), len, }; support::new_leak_box_ptr(ans) } #[no_mangle] pub extern "C" fn drop_opaque_Controls(ptr: *const c_void) { unsafe { Arc::::decrement_strong_count(ptr as _); } } #[no_mangle] pub extern "C" fn share_opaque_Controls(ptr: *const c_void) -> *const c_void { unsafe { Arc::::increment_strong_count(ptr as _); ptr } } impl Wire2Api> for wire_Controls { fn wire2api(self) -> RustOpaque { unsafe { support::opaque_from_dart(self.ptr as _) } } } impl Wire2Api for *mut wire_uint_8_list { fn wire2api(self) -> String { let vec: Vec = self.wire2api(); String::from_utf8_lossy(&vec).into_owned() } } impl Wire2Api for *mut i64 { fn wire2api(self) -> i64 { unsafe { *support::box_from_leak_ptr(self) } } } impl Wire2Api for *mut wire_Metadata { fn wire2api(self) -> Metadata { let wrap = unsafe { support::box_from_leak_ptr(self) }; Wire2Api::::wire2api(*wrap).into() } } impl Wire2Api for *mut wire_Player { fn wire2api(self) -> Player { let wrap = unsafe { support::box_from_leak_ptr(self) }; Wire2Api::::wire2api(*wrap).into() } } impl Wire2Api> for *mut wire_list_media_control_action { fn wire2api(self) -> Vec { let vec = unsafe { let wrap = support::box_from_leak_ptr(self); support::vec_from_leak_ptr(wrap.ptr, wrap.len) }; vec.into_iter().map(Wire2Api::wire2api).collect() } } impl Wire2Api for wire_Metadata { fn wire2api(self) -> Metadata { Metadata { title: self.title.wire2api(), artist: self.artist.wire2api(), album: self.album.wire2api(), art_uri: self.art_uri.wire2api(), art_bytes: self.art_bytes.wire2api(), } } } impl Wire2Api for wire_Player { fn wire2api(self) -> Player { Player { controls: self.controls.wire2api() } } } impl Wire2Api> for *mut wire_uint_8_list { fn wire2api(self) -> Vec { unsafe { let wrap = support::box_from_leak_ptr(self); support::vec_from_leak_ptr(wrap.ptr, wrap.len) } } } #[repr(C)] pub struct wire_Controls { ptr: *const core::ffi::c_void, } #[automatically_derived] impl ::core::clone::Clone for wire_Controls { #[inline] fn clone(&self) -> wire_Controls { wire_Controls { ptr: ::core::clone::Clone::clone(&self.ptr) } } } #[repr(C)] pub struct wire_list_media_control_action { ptr: *mut i32, len: i32, } #[automatically_derived] impl ::core::clone::Clone for wire_list_media_control_action { #[inline] fn clone(&self) -> wire_list_media_control_action { wire_list_media_control_action { ptr: ::core::clone::Clone::clone(&self.ptr), len: ::core::clone::Clone::clone(&self.len), } } } #[repr(C)] pub struct wire_Metadata { title: *mut wire_uint_8_list, artist: *mut wire_uint_8_list, album: *mut wire_uint_8_list, art_uri: *mut wire_uint_8_list, art_bytes: *mut wire_uint_8_list, } #[automatically_derived] impl ::core::clone::Clone for wire_Metadata { #[inline] fn clone(&self) -> wire_Metadata { wire_Metadata { title: ::core::clone::Clone::clone(&self.title), artist: ::core::clone::Clone::clone(&self.artist), album: ::core::clone::Clone::clone(&self.album), art_uri: ::core::clone::Clone::clone(&self.art_uri), art_bytes: ::core::clone::Clone::clone(&self.art_bytes), } } } #[repr(C)] pub struct wire_Player { controls: wire_Controls, } #[automatically_derived] impl ::core::clone::Clone for wire_Player { #[inline] fn clone(&self) -> wire_Player { wire_Player { controls: ::core::clone::Clone::clone(&self.controls), } } } #[repr(C)] pub struct wire_uint_8_list { ptr: *mut u8, len: i32, } #[automatically_derived] impl ::core::clone::Clone for wire_uint_8_list { #[inline] fn clone(&self) -> wire_uint_8_list { wire_uint_8_list { ptr: ::core::clone::Clone::clone(&self.ptr), len: ::core::clone::Clone::clone(&self.len), } } } pub trait NewWithNullPtr { fn new_with_null_ptr() -> Self; } impl NewWithNullPtr for *mut T { fn new_with_null_ptr() -> Self { std::ptr::null_mut() } } impl NewWithNullPtr for wire_Controls { fn new_with_null_ptr() -> Self { Self { ptr: core::ptr::null() } } } impl NewWithNullPtr for wire_Metadata { fn new_with_null_ptr() -> Self { Self { title: core::ptr::null_mut(), artist: core::ptr::null_mut(), album: core::ptr::null_mut(), art_uri: core::ptr::null_mut(), art_bytes: core::ptr::null_mut(), } } } impl Default for wire_Metadata { fn default() -> Self { Self::new_with_null_ptr() } } impl NewWithNullPtr for wire_Player { fn new_with_null_ptr() -> Self { Self { controls: wire_Controls::new_with_null_ptr() } } } impl Default for wire_Player { fn default() -> Self { Self::new_with_null_ptr() } } #[no_mangle] pub extern "C" fn free_WireSyncReturn(ptr: support::WireSyncReturn) { unsafe { let _ = support::box_from_leak_ptr(ptr); }; } } #[cfg(not(target_family = "wasm"))] pub use io::*; } mod media_controllers { use std::sync::RwLock; use self::types::{Event, MediaControlAction, MediaController, Metadata}; use crate::utils::types::PlaybackState; pub mod mpris { #![cfg(all(unix, not(target_os = "macos"), not(target_os = "android"), not(target_os = "ios")))] use std::{ collections::HashMap, sync::{Arc, Mutex}, thread, time::Duration, }; use crossbeam::channel::{unbounded, Receiver}; use zbus::{ blocking::ConnectionBuilder, dbus_interface, fdo::RequestNameFlags, zvariant::{ObjectPath, Value}, }; use crate::utils::types::PlaybackState; use super::types::{ Command, Event, MediaControlAction, MediaController, Metadata, }; pub struct Mpris { tx: crossbeam::channel::Sender, } impl MediaController for Mpris { fn set_metadata(&self, metadata: Metadata) { self.tx.send(Command::SetMetadata(metadata)).unwrap(); } fn set_position(&self, position: u64) { self.tx.send(Command::SetPosition(position)).unwrap(); } fn set_duration(&self, duration: u64) { self.tx.send(Command::SetDuration(duration)).unwrap(); } fn set_playback_state(&self, state: PlaybackState) { self.tx.send(Command::SetPlaybackState(state)).unwrap(); } fn stop(&self) { self.tx.send(Command::Stop).unwrap(); } } impl Mpris { pub fn new(actions: Vec, dbus_name: String, callback: C) -> Self where C: Fn(Event) + Send + 'static { let (tx, rx) = unbounded::(); thread::spawn(move || { pollster::block_on(Self::run(actions, dbus_name, rx, callback)).unwrap(); }); Mpris { tx } } async fn run(actions: Vec, dbus_name: String, rx: Receiver, callback: C) -> Result<(), zbus::Error> where C: Fn(Event) + Send + 'static { let bus_name = dbus_name.split('.').last().unwrap().to_string(); let app_interface = AppInterface { name: bus_name.clone() }; let player_interface = PlayerInterface { actions, callback: Arc::new(Mutex::new(callback)), metadata: Default::default(), playback_state: PlaybackState::Done, position: 0, duration: 0, }; let full_bus_name = { let res = ::alloc::fmt::format(format_args!("org.mpris.MediaPlayer2.{0}", bus_name)); res }; let bus_path = ObjectPath::try_from("/org/mpris/MediaPlayer2")?; let conn = ConnectionBuilder::session()?.serve_at(&bus_path, app_interface)?.serve_at(&bus_path, player_interface)?.build()?; conn.request_name_with_flags(full_bus_name.as_str(), RequestNameFlags::ReplaceExisting.into())?; loop { match rx.try_recv() { Err(_) => (), Ok(message) => { let player_iface_ref = conn.object_server().interface::<_, PlayerInterface>(&bus_path)?; let mut player_iface = player_iface_ref.get_mut(); let context = player_iface_ref.signal_context(); match message { Command::SetMetadata(data) => { player_iface.metadata = data; player_iface.metadata_changed(context).await?; } Command::SetPosition(position) => { player_iface.position = position; player_iface.position_changed(context).await?; } Command::SetDuration(duration) => { player_iface.duration = duration; player_iface.metadata_changed(context).await?; } Command::SetPlaybackState(state) => { player_iface.playback_state = state; player_iface.playback_status_changed(context).await?; } Command::Stop => break, } } } std::thread::sleep(Duration::from_millis(200)); } conn.release_name(full_bus_name)?; Ok(()) } } /// D-Bus interface that describes the app and its MPRIS capabilities /// (ex. can raise). struct AppInterface { name: String, } impl AppInterface { fn identity(&self) -> &str { &self.name } fn can_quit(&self) -> bool { false } fn can_raise(&self) -> bool { true } fn has_tracklist(&self) -> bool { false } fn raise(&self) { let process = std::env::current_exe().unwrap(); let _ = std::process::Command::new(process).spawn(); } } impl AppInterface { pub async fn identity_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.identity()); changed.insert("Identity", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &changed, &[]).await } pub async fn identity_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &::std::collections::HashMap::new(), &["Identity"]).await } pub async fn can_quit_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_quit()); changed.insert("CanQuit", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &changed, &[]).await } pub async fn can_quit_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &::std::collections::HashMap::new(), &["CanQuit"]).await } pub async fn can_raise_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_raise()); changed.insert("CanRaise", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &changed, &[]).await } pub async fn can_raise_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &::std::collections::HashMap::new(), &["CanRaise"]).await } pub async fn has_tracklist_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.has_tracklist()); changed.insert("HasTracklist", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &changed, &[]).await } pub async fn has_tracklist_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2"), &::std::collections::HashMap::new(), &["HasTracklist"]).await } } impl ::zbus::Interface for AppInterface { fn name() -> ::zbus::names::InterfaceName<'static> { ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2") } #[allow(clippy :: async_yields_async, clippy :: diverging_sub_expression, clippy :: let_unit_value, clippy :: no_effect_underscore_binding, clippy :: shadow_same, clippy :: type_complexity, clippy :: type_repetition_in_bounds, clippy :: used_underscore_binding)] fn get<'life0, 'life1, 'async_trait>(&'life0 self, property_name: &'life1 str) -> ::core::pin::Pin>> + ::core::marker::Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait { Box::pin(async move { if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<::std::option::Option<::zbus::fdo::Result<::zbus::zvariant::OwnedValue>>> { return __ret; } let __self = self; let __ret: ::std::option::Option<::zbus::fdo::Result<::zbus::zvariant::OwnedValue>> = { match property_name { "Identity" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.identity())))) } "CanQuit" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_quit())))) } "CanRaise" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_raise())))) } "HasTracklist" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.has_tracklist())))) } _ => ::std::option::Option::None, } }; #[allow(unreachable_code)] __ret }) } #[allow(clippy :: async_yields_async, clippy :: diverging_sub_expression, clippy :: let_unit_value, clippy :: no_effect_underscore_binding, clippy :: shadow_same, clippy :: type_complexity, clippy :: type_repetition_in_bounds, clippy :: used_underscore_binding)] fn get_all<'life0, 'async_trait>(&'life0 self) -> ::core::pin::Pin> + ::core::marker::Send + 'async_trait>> where 'life0: 'async_trait, Self: 'async_trait { Box::pin(async move { if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<::std::collections::HashMap<::std::string::String, ::zbus::zvariant::OwnedValue>> { return __ret; } let __self = self; let __ret: ::std::collections::HashMap<::std::string::String, ::zbus::zvariant::OwnedValue> = { let mut props: ::std::collections::HashMap<::std::string::String, ::zbus::zvariant::OwnedValue> = ::std::collections::HashMap::new(); props.insert(::std::string::ToString::to_string("Identity"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.identity()))); props.insert(::std::string::ToString::to_string("CanQuit"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_quit()))); props.insert(::std::string::ToString::to_string("CanRaise"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_raise()))); props.insert(::std::string::ToString::to_string("HasTracklist"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.has_tracklist()))); props }; #[allow(unreachable_code)] __ret }) } fn set<'call>(&'call self, property_name: &'call str, value: &'call ::zbus::zvariant::Value<'_>, signal_context: &'call ::zbus::SignalContext<'_>) -> ::zbus::DispatchResult<'call> { match property_name { _ => ::zbus::DispatchResult::NotFound, } } #[allow(clippy :: async_yields_async, clippy :: diverging_sub_expression, clippy :: let_unit_value, clippy :: no_effect_underscore_binding, clippy :: shadow_same, clippy :: type_complexity, clippy :: type_repetition_in_bounds, clippy :: used_underscore_binding)] fn set_mut<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(&'life0 mut self, property_name: &'life1 str, value: &'life2 ::zbus::zvariant::Value<'life3>, signal_context: &'life4 ::zbus::SignalContext<'life5>) -> ::core::pin::Pin>> + ::core::marker::Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, 'life4: 'async_trait, 'life5: 'async_trait, Self: 'async_trait { Box::pin(async move { if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<::std::option::Option<::zbus::fdo::Result<()>>> { return __ret; } let mut __self = self; let __ret: ::std::option::Option<::zbus::fdo::Result<()>> = { match property_name { _ => ::std::option::Option::None, } }; #[allow(unreachable_code)] __ret }) } fn call<'call>(&'call self, s: &'call ::zbus::ObjectServer, c: &'call ::zbus::Connection, m: &'call ::zbus::Message, name: ::zbus::names::MemberName<'call>) -> ::zbus::DispatchResult<'call> { match name.as_str() { "Raise" => { let future = async move { let reply = self.raise(); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } _ => ::zbus::DispatchResult::NotFound, } } fn call_mut<'call>(&'call mut self, s: &'call ::zbus::ObjectServer, c: &'call ::zbus::Connection, m: &'call ::zbus::Message, name: ::zbus::names::MemberName<'call>) -> ::zbus::DispatchResult<'call> { match name.as_str() { _ => ::zbus::DispatchResult::NotFound, } } fn introspect_to_writer(&self, writer: &mut dyn ::std::fmt::Write, level: usize) { writer.write_fmt(format_args!("{0:2$}\n", "", ::name(), level)).unwrap(); { use ::zbus::zvariant::Type; let level = level + 2; writer.write_fmt(format_args!("{0:2$}\n", "", "Raise", level)).unwrap(); { let level = level + 2; } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanQuit", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanRaise", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "HasTracklist", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "Identity", <&str>::signature(), "read", level)).unwrap(); } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); } } /// D-Bus interface that describes the app's player and its MPRIS capabilities /// (ex. play/pause). struct PlayerInterface { actions: Vec, callback: Arc>, position: u64, duration: u64, metadata: Metadata, playback_state: PlaybackState, } impl PlayerInterface { fn can_control(&self) -> bool { true } fn can_seek(&self) -> bool { true } fn can_play(&self) -> bool { true } fn can_pause(&self) -> bool { true } fn can_go_previous(&self) -> bool { self.actions.contains(&MediaControlAction::SkipPrev) } fn can_go_next(&self) -> bool { self.actions.contains(&MediaControlAction::SkipNext) } fn position(&self) -> i64 { let in_micros = Duration::from_secs(self.position).as_micros(); in_micros.try_into().unwrap_or_default() } fn playback_status(&self) -> &'static str { match self.playback_state { PlaybackState::Play => "Playing", PlaybackState::Pause => "Paused", _ => "Paused", } } fn metadata(&self) -> HashMap<&str, Value> { let mut map = HashMap::<&str, Value>::new(); let path = ObjectPath::try_from("/").unwrap(); map.insert("mpris:trackid", Value::new(path)); if let Some(title) = self.metadata.title.clone() { map.insert("xesam:title", Value::new(title)); } if let Some(artist) = self.metadata.artist.clone() { map.insert("xesam:artist", Value::new(<[_]>::into_vec(#[rustc_box] ::alloc::boxed::Box::new([artist])))); } if let Some(album) = self.metadata.album.clone() { map.insert("xesam:album", Value::new(album)); } if let Some(art_uri) = self.metadata.art_uri.clone() { map.insert("mpris:artUrl", Value::new(art_uri)); } let in_micros: i64 = Duration::from_secs(self.duration).as_micros().try_into().unwrap_or_default(); map.insert("mpris:length", Value::new(in_micros)); map } fn seek(&self, offset: i64) { let in_seconds = Duration::from_micros(offset.unsigned_abs()).as_secs().try_into().unwrap_or_default(); if offset.is_positive() && self.actions.contains(&MediaControlAction::FastForward) { self.callback.lock().unwrap()(Event::Seek(in_seconds, false)); } else if offset.is_negative() && self.actions.contains(&MediaControlAction::Rewind) { self.callback.lock().unwrap()(Event::Seek(-in_seconds, false)); } } fn set_position(&self, _track_id: ObjectPath, position: i64) { let in_seconds = Duration::from_micros(position.unsigned_abs()).as_secs().try_into().unwrap_or_default(); self.callback.lock().unwrap()(Event::Seek(in_seconds, true)); } fn stop(&self) { self.callback.lock().unwrap()(Event::Stop); } fn previous(&self) { self.callback.lock().unwrap()(Event::Previous); } fn play(&self) { self.callback.lock().unwrap()(Event::Play); } fn pause(&self) { self.callback.lock().unwrap()(Event::Pause); } fn play_pause(&self) { self.callback.lock().unwrap()(Event::PlayPause); } fn next(&self) { self.callback.lock().unwrap()(Event::Next); } } impl PlayerInterface { pub async fn can_control_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_control()); changed.insert("CanControl", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn can_control_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["CanControl"]).await } pub async fn can_seek_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_seek()); changed.insert("CanSeek", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn can_seek_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["CanSeek"]).await } pub async fn can_play_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_play()); changed.insert("CanPlay", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn can_play_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["CanPlay"]).await } pub async fn can_pause_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_pause()); changed.insert("CanPause", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn can_pause_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["CanPause"]).await } pub async fn can_go_previous_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_go_previous()); changed.insert("CanGoPrevious", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn can_go_previous_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["CanGoPrevious"]).await } pub async fn can_go_next_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.can_go_next()); changed.insert("CanGoNext", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn can_go_next_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["CanGoNext"]).await } pub async fn position_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.position()); changed.insert("Position", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn position_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["Position"]).await } pub async fn playback_status_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.playback_status()); changed.insert("PlaybackStatus", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn playback_status_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["PlaybackStatus"]).await } pub async fn metadata_changed(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { let mut changed = ::std::collections::HashMap::new(); let value = <::zbus::zvariant::Value as ::std::convert::From<_>>::from(self.metadata()); changed.insert("Metadata", &value); ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &changed, &[]).await } pub async fn metadata_invalidate(&self, signal_context: &::zbus::SignalContext<'_>) -> ::zbus::Result<()> { ::zbus::fdo::Properties::properties_changed(signal_context, ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player"), &::std::collections::HashMap::new(), &["Metadata"]).await } } impl ::zbus::Interface for PlayerInterface { fn name() -> ::zbus::names::InterfaceName<'static> { ::zbus::names::InterfaceName::from_static_str_unchecked("org.mpris.MediaPlayer2.Player") } #[allow(clippy :: async_yields_async, clippy :: diverging_sub_expression, clippy :: let_unit_value, clippy :: no_effect_underscore_binding, clippy :: shadow_same, clippy :: type_complexity, clippy :: type_repetition_in_bounds, clippy :: used_underscore_binding)] fn get<'life0, 'life1, 'async_trait>(&'life0 self, property_name: &'life1 str) -> ::core::pin::Pin>> + ::core::marker::Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, Self: 'async_trait { Box::pin(async move { if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<::std::option::Option<::zbus::fdo::Result<::zbus::zvariant::OwnedValue>>> { return __ret; } let __self = self; let __ret: ::std::option::Option<::zbus::fdo::Result<::zbus::zvariant::OwnedValue>> = { match property_name { "CanControl" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_control())))) } "CanSeek" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_seek())))) } "CanPlay" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_play())))) } "CanPause" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_pause())))) } "CanGoPrevious" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_go_previous())))) } "CanGoNext" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_go_next())))) } "Position" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.position())))) } "PlaybackStatus" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.playback_status())))) } "Metadata" => { ::std::option::Option::Some(::std::result::Result::Ok(::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.metadata())))) } _ => ::std::option::Option::None, } }; #[allow(unreachable_code)] __ret }) } #[allow(clippy :: async_yields_async, clippy :: diverging_sub_expression, clippy :: let_unit_value, clippy :: no_effect_underscore_binding, clippy :: shadow_same, clippy :: type_complexity, clippy :: type_repetition_in_bounds, clippy :: used_underscore_binding)] fn get_all<'life0, 'async_trait>(&'life0 self) -> ::core::pin::Pin> + ::core::marker::Send + 'async_trait>> where 'life0: 'async_trait, Self: 'async_trait { Box::pin(async move { if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<::std::collections::HashMap<::std::string::String, ::zbus::zvariant::OwnedValue>> { return __ret; } let __self = self; let __ret: ::std::collections::HashMap<::std::string::String, ::zbus::zvariant::OwnedValue> = { let mut props: ::std::collections::HashMap<::std::string::String, ::zbus::zvariant::OwnedValue> = ::std::collections::HashMap::new(); props.insert(::std::string::ToString::to_string("CanControl"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_control()))); props.insert(::std::string::ToString::to_string("CanSeek"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_seek()))); props.insert(::std::string::ToString::to_string("CanPlay"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_play()))); props.insert(::std::string::ToString::to_string("CanPause"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_pause()))); props.insert(::std::string::ToString::to_string("CanGoPrevious"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_go_previous()))); props.insert(::std::string::ToString::to_string("CanGoNext"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.can_go_next()))); props.insert(::std::string::ToString::to_string("Position"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.position()))); props.insert(::std::string::ToString::to_string("PlaybackStatus"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.playback_status()))); props.insert(::std::string::ToString::to_string("Metadata"), ::std::convert::Into::into(<::zbus::zvariant::Value as ::std::convert::From<_>>::from(__self.metadata()))); props }; #[allow(unreachable_code)] __ret }) } fn set<'call>(&'call self, property_name: &'call str, value: &'call ::zbus::zvariant::Value<'_>, signal_context: &'call ::zbus::SignalContext<'_>) -> ::zbus::DispatchResult<'call> { match property_name { _ => ::zbus::DispatchResult::NotFound, } } #[allow(clippy :: async_yields_async, clippy :: diverging_sub_expression, clippy :: let_unit_value, clippy :: no_effect_underscore_binding, clippy :: shadow_same, clippy :: type_complexity, clippy :: type_repetition_in_bounds, clippy :: used_underscore_binding)] fn set_mut<'life0, 'life1, 'life2, 'life3, 'life4, 'life5, 'async_trait>(&'life0 mut self, property_name: &'life1 str, value: &'life2 ::zbus::zvariant::Value<'life3>, signal_context: &'life4 ::zbus::SignalContext<'life5>) -> ::core::pin::Pin>> + ::core::marker::Send + 'async_trait>> where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, 'life3: 'async_trait, 'life4: 'async_trait, 'life5: 'async_trait, Self: 'async_trait { Box::pin(async move { if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::<::std::option::Option<::zbus::fdo::Result<()>>> { return __ret; } let mut __self = self; let __ret: ::std::option::Option<::zbus::fdo::Result<()>> = { match property_name { _ => ::std::option::Option::None, } }; #[allow(unreachable_code)] __ret }) } fn call<'call>(&'call self, s: &'call ::zbus::ObjectServer, c: &'call ::zbus::Connection, m: &'call ::zbus::Message, name: ::zbus::names::MemberName<'call>) -> ::zbus::DispatchResult<'call> { match name.as_str() { "Seek" => { let future = async move { let (offset): (i64) = match m.body() { ::std::result::Result::Ok(r) => r, ::std::result::Result::Err(e) => { let hdr = m.header()?; let err = <::zbus::fdo::Error as ::std::convert::From<_>>::from(e); return c.reply_dbus_error(&hdr, err).await; } }; let reply = self.seek(offset); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } "SetPosition" => { let future = async move { let (_track_id, position): (ObjectPath, i64) = match m.body() { ::std::result::Result::Ok(r) => r, ::std::result::Result::Err(e) => { let hdr = m.header()?; let err = <::zbus::fdo::Error as ::std::convert::From<_>>::from(e); return c.reply_dbus_error(&hdr, err).await; } }; let reply = self.set_position(_track_id, position); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } "Stop" => { let future = async move { let reply = self.stop(); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } "Previous" => { let future = async move { let reply = self.previous(); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } "Play" => { let future = async move { let reply = self.play(); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } "Pause" => { let future = async move { let reply = self.pause(); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } "PlayPause" => { let future = async move { let reply = self.play_pause(); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } "Next" => { let future = async move { let reply = self.next(); c.reply(m, &reply).await }; ::zbus::DispatchResult::Async(::std::boxed::Box::pin(async move { future.await.map(|_seq: u32| ()) })) } _ => ::zbus::DispatchResult::NotFound, } } fn call_mut<'call>(&'call mut self, s: &'call ::zbus::ObjectServer, c: &'call ::zbus::Connection, m: &'call ::zbus::Message, name: ::zbus::names::MemberName<'call>) -> ::zbus::DispatchResult<'call> { match name.as_str() { _ => ::zbus::DispatchResult::NotFound, } } fn introspect_to_writer(&self, writer: &mut dyn ::std::fmt::Write, level: usize) { writer.write_fmt(format_args!("{0:2$}\n", "", ::name(), level)).unwrap(); { use ::zbus::zvariant::Type; let level = level + 2; writer.write_fmt(format_args!("{0:2$}\n", "", "Seek", level)).unwrap(); { let level = level + 2; writer.write_fmt(format_args!("{0:4$}\n", "", "offset", ::signature(), " direction=\"in\"", level)).unwrap(); } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:2$}\n", "", "SetPosition", level)).unwrap(); { let level = level + 2; writer.write_fmt(format_args!("{0:4$}\n", "", "_track_id", ::signature(), " direction=\"in\"", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "position", ::signature(), " direction=\"in\"", level)).unwrap(); } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:2$}\n", "", "Stop", level)).unwrap(); { let level = level + 2; } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:2$}\n", "", "Previous", level)).unwrap(); { let level = level + 2; } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:2$}\n", "", "Play", level)).unwrap(); { let level = level + 2; } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:2$}\n", "", "Pause", level)).unwrap(); { let level = level + 2; } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:2$}\n", "", "PlayPause", level)).unwrap(); { let level = level + 2; } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:2$}\n", "", "Next", level)).unwrap(); { let level = level + 2; } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanControl", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanGoNext", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanGoPrevious", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanPause", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanPlay", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "CanSeek", ::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "Metadata", >::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "PlaybackStatus", <&'static str>::signature(), "read", level)).unwrap(); writer.write_fmt(format_args!("{0:4$}\n", "", "Position", ::signature(), "read", level)).unwrap(); } writer.write_fmt(format_args!("{0:1$}\n", "", level)).unwrap(); } } } pub mod types { use crate::utils::types::PlaybackState; pub trait MediaController: Send + Sync { fn set_metadata(&self, metadata: Metadata); fn set_position(&self, position: u64); fn set_duration(&self, duration: u64); fn set_playback_state(&self, state: PlaybackState); fn stop(&self); } /// The metadata of the currently playing file /// that will be shown in the OS's media controller. pub struct Metadata { /// The title of the file. pub title: Option, /// The artist/creator of the file. pub artist: Option, /// The album that the song is in. pub album: Option, /// A URI that points to the art for this song. pub art_uri: Option, /// The song's art in the form of a byte array. pub art_bytes: Option>, } #[automatically_derived] impl ::core::default::Default for Metadata { #[inline] fn default() -> Metadata { Metadata { title: ::core::default::Default::default(), artist: ::core::default::Default::default(), album: ::core::default::Default::default(), art_uri: ::core::default::Default::default(), art_bytes: ::core::default::Default::default(), } } } /// The actions that an OS's media controller can support. pub enum MediaControlAction { /// Seeks backwards by 10 seconds. Rewind, /// Skip to the previous playing file (you will implement this functionality). SkipPrev, /// Play/pause the player. PlayPause, /// Skip to the next file to be played (you will implement this functionality). SkipNext, /// Seeks forwards by 10 seconds. FastForward, } #[automatically_derived] impl ::core::marker::StructuralPartialEq for MediaControlAction { } #[automatically_derived] impl ::core::cmp::PartialEq for MediaControlAction { #[inline] fn eq(&self, other: &MediaControlAction) -> bool { let __self_tag = ::core::intrinsics::discriminant_value(self); let __arg1_tag = ::core::intrinsics::discriminant_value(other); __self_tag == __arg1_tag } } #[automatically_derived] impl ::core::marker::StructuralEq for MediaControlAction { } #[automatically_derived] impl ::core::cmp::Eq for MediaControlAction { #[inline] #[doc(hidden)] #[no_coverage] fn assert_receiver_is_total_eq(&self) -> () {} } #[automatically_derived] impl ::core::clone::Clone for MediaControlAction { #[inline] fn clone(&self) -> MediaControlAction { *self } } #[automatically_derived] impl ::core::marker::Copy for MediaControlAction { } impl From for MediaControlAction { fn from(i: i32) -> Self { match i { 0 => Self::Rewind, 1 => Self::SkipPrev, 2 => Self::PlayPause, 3 => Self::SkipNext, 4 => Self::FastForward, _ => { ::core::panicking::panic_fmt(format_args!("ERR: This action is not supported.")); } } } } /// Callback events from the media notification. pub enum Event { Next, Previous, Play, Pause, Stop, PlayPause, /// `i64`: Position. /// /// `bool`: Is absolute. /// If `true`, the position is between `0-duration`. /// If false, the position can be negative to indicate going backwards. Seek(i64, bool), } #[automatically_derived] impl ::core::clone::Clone for Event { #[inline] fn clone(&self) -> Event { let _: ::core::clone::AssertParamIsClone; let _: ::core::clone::AssertParamIsClone; *self } } #[automatically_derived] impl ::core::marker::Copy for Event { } #[automatically_derived] impl ::core::fmt::Debug for Event { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { match self { Event::Next => ::core::fmt::Formatter::write_str(f, "Next"), Event::Previous => ::core::fmt::Formatter::write_str(f, "Previous"), Event::Play => ::core::fmt::Formatter::write_str(f, "Play"), Event::Pause => ::core::fmt::Formatter::write_str(f, "Pause"), Event::Stop => ::core::fmt::Formatter::write_str(f, "Stop"), Event::PlayPause => ::core::fmt::Formatter::write_str(f, "PlayPause"), Event::Seek(__self_0, __self_1) => ::core::fmt::Formatter::debug_tuple_field2_finish(f, "Seek", __self_0, &__self_1), } } } #[cfg(all(unix, not(target_os = "macos"), not(target_os = "android"), not(target_os = "ios")))] /// Commands to be sent via the thread's channels for MPRIS. pub enum Command { SetMetadata(Metadata), SetPosition(u64), SetDuration(u64), SetPlaybackState(PlaybackState), Stop, } } static MEDIA_CONTROLLER: RwLock>> = RwLock::new(None); /// Initialize a platform specific metadata handler. #[allow(unused_variables)] pub fn init(actions: Vec, dbus_name: String, hwnd: Option, callback: C) where C: Fn(Event) + Send + 'static { if actions.is_empty() { return; } #[cfg(all(unix, not(target_os = "macos"), not(target_os = "android"), not(target_os = "ios")))] { let mut lock = MEDIA_CONTROLLER.write().unwrap(); *lock = Some(Box::new(mpris::Mpris::new(actions, dbus_name, callback))); } } /// Stops the OS's media controller. pub fn dispose() { let mut lock = MEDIA_CONTROLLER.write().unwrap(); if lock.is_none() { return; } let controller = (*lock).take().unwrap(); controller.stop(); } pub fn set_metadata(metadata: Metadata) { let lock = MEDIA_CONTROLLER.read().unwrap(); if lock.is_none() { return; } lock.as_ref().unwrap().set_metadata(metadata); } /// Sets the current position of playback for the OS's /// media controller. This should be called once every second. pub fn set_position(position: u64) { let lock = MEDIA_CONTROLLER.read().unwrap(); if lock.is_none() { return; } lock.as_ref().unwrap().set_position(position); } /// Sets the file's duration for the OS's media controller. /// /// This should be called as soon as the duration is calculated /// in the decoder. pub fn set_duration(duration: u64) { let lock = MEDIA_CONTROLLER.read().unwrap(); if lock.is_none() { return; } lock.as_ref().unwrap().set_duration(duration); } pub fn set_playback_state(state: PlaybackState) { let lock = MEDIA_CONTROLLER.read().unwrap(); if lock.is_none() { return; } lock.as_ref().unwrap().set_playback_state(state); } } mod utils { pub mod blocking_rb { use std::sync::{atomic::AtomicUsize, Arc, Condvar, Mutex}; /// Provides the producer methods of the ring buffer. pub struct Producer; #[automatically_derived] impl ::core::clone::Clone for Producer { #[inline] fn clone(&self) -> Producer { Producer } } /// Provides the consumer methods of the ring buffer. pub struct Consumer; #[automatically_derived] impl ::core::clone::Clone for Consumer { #[inline] fn clone(&self) -> Consumer { Consumer } } pub struct BlockingRb { size: usize, num_values: Arc, buf: Arc>>, read_pos: Arc, write_pos: Arc, producer_events: Arc<(Mutex, Condvar)>, _type: std::marker::PhantomData, } #[automatically_derived] impl ::core::clone::Clone for BlockingRb { #[inline] fn clone(&self) -> BlockingRb { BlockingRb { size: ::core::clone::Clone::clone(&self.size), num_values: ::core::clone::Clone::clone(&self.num_values), buf: ::core::clone::Clone::clone(&self.buf), read_pos: ::core::clone::Clone::clone(&self.read_pos), write_pos: ::core::clone::Clone::clone(&self.write_pos), producer_events: ::core::clone::Clone::clone(&self.producer_events), _type: ::core::clone::Clone::clone(&self._type), } } } impl BlockingRb { /// Returns a producer and a consumer tuple. pub fn new(size: usize) -> (BlockingRb, BlockingRb) { let num_values = Arc::new(AtomicUsize::new(0)); let buf = Arc::new(Mutex::new(::alloc::vec::from_elem(T::default(), size))); let read_pos = Arc::new(AtomicUsize::new(0)); let write_pos = Arc::new(AtomicUsize::new(0)); let producer_events = Arc::new((Mutex::new(Event::None), Condvar::new())); (BlockingRb { size, num_values: num_values.clone(), buf: buf.clone(), read_pos: read_pos.clone(), write_pos: write_pos.clone(), producer_events: producer_events.clone(), _type: std::marker::PhantomData::, }, BlockingRb { size, num_values, buf, read_pos, write_pos, producer_events, _type: std::marker::PhantomData::, }) } /// Returns the number of free spaces in the ring buffer. fn num_free(&self) -> usize { let num_values = self.num_values.load(std::sync::atomic::Ordering::SeqCst); self.size - num_values } fn is_full(&self) -> bool { let num_values = self.num_values.load(std::sync::atomic::Ordering::SeqCst); num_values == self.size } fn is_empty(&self) -> bool { let num_values = self.num_values.load(std::sync::atomic::Ordering::SeqCst); num_values == 0 } } impl BlockingRb { /// Blocks the thread until there is space in the /// buffer to write to. This operation can be cancelled /// by calling `cancel`. /// /// Returns the number of items written. /// Returns `None` if the given slice is empty /// or the operation was cancelled. pub fn write(&self, slice: &[T]) -> Option { if slice.is_empty() { return None; } let num_free = self.num_free(); if num_free < slice.len() || self.is_full() { let (mutex, cvar) = &*self.producer_events; let mut event = mutex.lock().unwrap(); event = cvar.wait(event).unwrap(); match *event { Event::CancelWrite => return None, Event::FreeSpace => (), _ => { ::core::panicking::panic_fmt(format_args!("This event is not supported by `write()`.")); } } } let mut buf = self.buf.lock().unwrap(); let count = slice.len().min(num_free); let write_pos = self.write_pos.load(std::sync::atomic::Ordering::SeqCst); if write_pos + count < self.size { buf[write_pos..write_pos + count].copy_from_slice(&slice[..count]); } else { let num_end = self.size - write_pos; buf[write_pos..].copy_from_slice(&slice[..num_end]); buf[..count - num_end].copy_from_slice(&slice[num_end..count]); } let write_pos = (write_pos + count) % self.size; self.write_pos.store(write_pos, std::sync::atomic::Ordering::SeqCst); self.num_values.fetch_add(count, std::sync::atomic::Ordering::SeqCst); Some(count) } /// Cancels the current write operation. pub fn cancel_write(&self) { let (mutex, cvar) = &*self.producer_events; *mutex.lock().unwrap() = Event::CancelWrite; cvar.notify_all(); } } impl BlockingRb { /// Reads from the ring buffer and fills the given slice /// with as much data as possible. /// /// Returns the number of items written. /// Returns `None` if the given slice is empty /// or the buffer is empty. pub fn read(&self, slice: &mut [T]) -> Option { if slice.is_empty() || self.is_empty() { return None; } let buf = self.buf.lock().unwrap(); let count = slice.len().min(self.size); let read_pos = self.read_pos.load(std::sync::atomic::Ordering::SeqCst); if read_pos + count < self.size { slice[..count].copy_from_slice(&buf[read_pos..read_pos + count]); } else { let num_end = self.size - read_pos; slice[..num_end].copy_from_slice(&buf[read_pos..]); slice[num_end..count].copy_from_slice(&buf[..count - num_end]); } self.read_pos.store((read_pos + count) % self.size, std::sync::atomic::Ordering::SeqCst); let num_values = self.num_values.load(std::sync::atomic::Ordering::SeqCst); self.num_values.store(num_values.saturating_sub(count), std::sync::atomic::Ordering::SeqCst); let (mutex, cvar) = &*self.producer_events; *mutex.lock().unwrap() = Event::FreeSpace; cvar.notify_all(); Some(count) } /// Sets the read position to the write position. /// This lets the consumer skip reading all the data /// in between in case it is useless. pub fn skip_all(&self) { let write_pos = self.write_pos.load(std::sync::atomic::Ordering::SeqCst); self.read_pos.store(write_pos, std::sync::atomic::Ordering::SeqCst); self.num_values.store(0, std::sync::atomic::Ordering::SeqCst); let (mutex, cvar) = &*self.producer_events; *mutex.lock().unwrap() = Event::FreeSpace; cvar.notify_all(); } } /// Ring buffer events. enum Event { None, /// There is free space in the buffer (sent after the buffer was read). FreeSpace, /// The write operation has been cancelled. CancelWrite, } #[automatically_derived] impl ::core::clone::Clone for Event { #[inline] fn clone(&self) -> Event { *self } } #[automatically_derived] impl ::core::marker::Copy for Event { } } pub mod callback_stream { use std::sync::{OnceLock, RwLock}; use flutter_rust_bridge::StreamSink; use super::types::Callback; static CALLBACK_STREAM: OnceLock>>> = OnceLock::new(); /// Creates a new stream for sending callbacks to Dart. pub fn callback_stream(stream: StreamSink) { *CALLBACK_STREAM.get_or_init(|| RwLock::new(None)).write().unwrap() = Some(stream); } /// Updates/adds to the stream with the given value. pub fn update_callback_stream(value: Callback) { if let Some(lock) = CALLBACK_STREAM.get() { if let Some(stream) = &*lock.read().unwrap() { stream.add(value); } } } } pub mod playback_state_stream { use std::sync::{OnceLock, RwLock}; use flutter_rust_bridge::StreamSink; use super::types::PlaybackState; static PLAYBACK_STATE_STREAM: OnceLock>>> = OnceLock::new(); /// Creates a new playback stream. pub fn playback_state_stream(stream: StreamSink) { *PLAYBACK_STATE_STREAM.get_or_init(|| RwLock::new(None)).write().unwrap() = Some(stream); } /// Updates/adds to the stream with the given value. pub fn update_playback_state_stream(value: PlaybackState) { if let Some(lock) = PLAYBACK_STATE_STREAM.get() { if let Some(stream) = &*lock.read().unwrap() { stream.add(value); } } } } pub mod progress_state_stream { use std::sync::{OnceLock, RwLock}; use flutter_rust_bridge::StreamSink; use super::types::ProgressState; static PROGRESS_STATE_STREAM: OnceLock>>> = OnceLock::new(); /// Creates a new progress stream. pub fn progress_state_stream(stream: StreamSink) { *PROGRESS_STATE_STREAM.get_or_init(|| RwLock::new(None)).write().unwrap() = Some(stream); } /// Updates/adds to the stream with the given value. pub fn update_progress_state_stream(value: ProgressState) { if let Some(lock) = PROGRESS_STATE_STREAM.get() { if let Some(stream) = &*lock.read().unwrap() { stream.add(value); } } } } pub mod types { /// The playback state of the player. pub enum PlaybackState { /// The player is currently playing the file. Play = 0, /// The player is currently paused and there is no output. Pause = 1, /// The player has finished playing the file. Done = 2, } /// Provides the current progress of the player. pub struct ProgressState { /// The position, in seconds, of the player. pub position: u64, /// The duration, in seconds, of the file that /// is being played. pub duration: u64, } #[automatically_derived] impl ::core::clone::Clone for ProgressState { #[inline] fn clone(&self) -> ProgressState { let _: ::core::clone::AssertParamIsClone; *self } } #[automatically_derived] impl ::core::marker::Copy for ProgressState { } /// Events that are handled in Dart because they /// cannot be handled in Rust or need user action. pub enum Callback { /// The media controller trigged the SkipPrev action. MediaControlSkipPrev, /// The media controller trigged the SkipNext action. MediaControlSkipNext, /// An error occurred when trying to fetch more bytes for /// a network stream. NetworkStreamError, /// An error occurred when decoding the file. DecodeError, /// The player is in the looping mode and the playback /// just looped to the beginning. PlaybackLooped, /// The decoder has calculated the duration for the current playback. DurationCalculated, } } } use std::{ fs::File, sync::{atomic::AtomicBool, Arc, RwLock}, thread, }; use anyhow::Context; use audio::{ controls::*, decoder::Decoder, sources::{hls::HlsStream, http::HttpStream}, }; use crossbeam::channel::unbounded; use flutter_rust_bridge::{RustOpaque, StreamSink}; use media_controllers::types::{Event, MediaControlAction, Metadata}; use symphonia::core::io::MediaSource; use utils::types::*; use crate::utils::{ callback_stream::*, playback_state_stream::*, progress_state_stream::*, }; pub struct Player { controls: RustOpaque, } impl Player { pub fn new(actions: Vec, dbus_name: String, hwnd: Option) -> Player { let player_controls = Controls::default(); media_controllers::init(actions, dbus_name, hwnd, { let controls = player_controls.clone(); move |e| match e { Event::Previous => update_callback_stream(Callback::MediaControlSkipPrev), Event::Next => update_callback_stream(Callback::MediaControlSkipNext), Event::Play => Self::internal_play(&controls), Event::Pause => Self::internal_pause(&controls), Event::Stop => Self::internal_stop(&controls), Event::PlayPause => { if controls.is_playing() { Self::internal_pause(&controls); } else { Self::internal_play(&controls); } } Event::Seek(position, is_absolute) => { if is_absolute { Self::internal_seek(&controls, position as u64); } else { let progress = controls.progress(); if position.is_negative() { Self::internal_seek(&controls, progress.position.saturating_sub(position.unsigned_abs())); } else { Self::internal_seek(&controls, progress.position + position as u64); } } } } }); *THREAD_KILLER.get_or_init(|| RwLock::new(unbounded())).write().unwrap() = unbounded(); thread::spawn({ let controls = player_controls.clone(); move || { let decoder = Decoder::new(controls); decoder.start(); } }); Player { controls: RustOpaque::new(player_controls) } } /// Stops media controllers and decoder threads. pub fn dispose() { if let Some(thread_killer) = THREAD_KILLER.get() { thread_killer.read().unwrap().0.send(true).unwrap(); } media_controllers::dispose(); } pub fn playback_state_stream(stream: StreamSink) { playback_state_stream(stream); } pub fn progress_state_stream(stream: StreamSink) { progress_state_stream(stream); } pub fn callback_stream(stream: StreamSink) { callback_stream(stream); } pub fn is_playing(&self) -> bool { self.controls.is_playing() } /// Returns `true` if there is a file preloaded for playback. pub fn has_preloaded(&self) -> bool { self.controls.is_file_preloaded() } pub fn get_progress(&self) -> ProgressState { *self.controls.progress() } /// Returns a Symphonia `MediaSource` for playback. fn source_from_path(path: String, buffer_signal: Arc) -> anyhow::Result> { let path2 = path.clone(); let source: Box = if path.contains("http") { if path.contains("m3u") { Box::new(HlsStream::new(path, buffer_signal).context({ let res = ::alloc::fmt::format(format_args!("Could not open HLS stream at \"{0}\"", path2)); res })?) } else { Box::new(HttpStream::new(path, buffer_signal).context({ let res = ::alloc::fmt::format(format_args!("Could not open HTTP stream at \"{0}\"", path2)); res })?) } } else { let file = File::open(path).context({ let res = ::alloc::fmt::format(format_args!("Could not open file at \"{0}\"", path2)); res })?; Box::new(file) }; Ok(source) } /// Opens a file or network resource for reading and playing. pub fn open(&self, path: String, autoplay: bool) -> anyhow::Result<()> { let buffer_signal = Arc::new(AtomicBool::new(false)); let source = Self::source_from_path(path, buffer_signal.clone())?; self.controls.event_handler().send(PlayerEvent::Open(source, buffer_signal))?; if autoplay { Self::internal_play(&self.controls); } else { Self::internal_pause(&self.controls); } Ok(()) } /// Preloads a file or network resource for reading and playing. /// /// Use this method if you want gapless playback. It reduces /// the time spent loading between tracks (especially important /// for streaming network files). pub fn preload(&self, path: String) -> anyhow::Result<()> { let buffer_signal = Arc::new(AtomicBool::new(false)); let source = Self::source_from_path(path, buffer_signal.clone())?; self.controls.event_handler().send(PlayerEvent::Preload(source, buffer_signal))?; Ok(()) } /// Plays the preloaded item from `preload`. The file starts playing automatically. pub fn play_preload(&self) -> anyhow::Result<()> { self.controls.event_handler().send(PlayerEvent::PlayPreload)?; Ok(()) } /// Allows for access in other places /// where we would want to update the stream and /// the `IS_PLAYING` AtomicBool. fn internal_play(controls: &Controls) { if controls.is_playing() { return; } controls.event_handler().send(PlayerEvent::Play).unwrap(); update_playback_state_stream(PlaybackState::Play); controls.set_is_playing(true); controls.set_is_stopped(false); media_controllers::set_playback_state(PlaybackState::Play); } /// Allows for access in other places /// where we would want to update the stream and /// the `IS_PLAYING` AtomicBool. fn internal_pause(controls: &Controls) { if !controls.is_playing() { return; } controls.event_handler().send(PlayerEvent::Pause).unwrap(); update_playback_state_stream(PlaybackState::Pause); controls.set_is_playing(false); controls.set_is_stopped(false); media_controllers::set_playback_state(PlaybackState::Pause); } /// Allows for access in other places /// where we would want to update the stream and /// the `IS_PLAYING` AtomicBool. /// This stops all threads that are streaming. fn internal_stop(controls: &Controls) { if controls.is_stopped() { return; } controls.event_handler().send(PlayerEvent::Stop).unwrap(); let progress = ProgressState { position: 0, duration: 0 }; update_progress_state_stream(progress); controls.set_progress(progress); update_playback_state_stream(PlaybackState::Pause); controls.set_is_playing(false); controls.set_is_stopped(true); media_controllers::set_playback_state(PlaybackState::Pause); } fn internal_seek(controls: &Controls, seconds: u64) { controls.set_seek_ts(Some(seconds)); update_progress_state_stream(ProgressState { position: seconds, duration: controls.progress().duration, }); } pub fn play(&self) { Self::internal_play(&self.controls); } pub fn pause(&self) { Self::internal_pause(&self.controls); } pub fn stop(&self) { Self::internal_stop(&self.controls); } pub fn loop_playback(&self, should_loop: bool) { self.controls.set_is_looping(should_loop); } pub fn set_volume(&self, volume: f32) { self.controls.set_volume(volume); } pub fn seek(&self, seconds: u64) { Self::internal_seek(&self.controls, seconds); } pub fn set_metadata(&self, metadata: Metadata) { media_controllers::set_metadata(metadata); } pub fn normalize_volume(&self, should_normalize: bool) { self.controls.set_is_normalizing(should_normalize); } } impl Default for Player { fn default() -> Self { crate::Player::new(<[_]>::into_vec(#[rustc_box] ::alloc::boxed::Box::new([MediaControlAction::Rewind, MediaControlAction::SkipPrev, MediaControlAction::PlayPause, MediaControlAction::SkipNext, MediaControlAction::FastForward])), "com.erikas.SimpleAudio".to_string(), None) } } stderr=warning: unused import: `std::process::Command` --> build.rs:17:5 | 17 | use std::process::Command; | ^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default Compiling simple_audio v1.6.6 (/home/erikas/ROOT/code/flutter-plugins/simple_audio/rust) Finished dev [unoptimized + debuginfo] target(s) in 0.20s ```
fzyzcjy commented 12 months ago

@erikas-taroza Hi, could you please create a minimal reproducible sample? e.g. bisect the code, etc.

Alternatively, maybe we should enhance frb's error message: When "lex error" happens, we should use anyhow's context feature to attach some context to it, such as the surrounding code that is being parsed by frb.

erikas-taroza commented 12 months ago

@erikas-taroza Hi, could you please create a minimal reproducible sample? e.g. bisect the code, etc.

Sure.

Alternatively, maybe we should enhance frb's error message: When "lex error" happens, we should use anyhow's context feature to attach some context to it, such as the surrounding code that is being parsed by frb.

Yes, this is a good idea.

erikas-taroza commented 12 months ago

I found the issue.

https://github.com/fzyzcjy/flutter_rust_bridge/blob/d54eae758f94ca1a5e69787d20f16af39daa89c2/frb_codegen/src/commands.rs#L321

This was taking a } off from the end of the expanded file which was causing the error.

fzyzcjy commented 12 months ago

Great observation! Feel free to PR to fix it

erikas-taroza commented 12 months ago

Great observation! Feel free to PR to fix it

Created :) #1372

github-actions[bot] commented 11 months ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new issue.