Skip to main content

novel_tts/queue/
queue_output.rs

1//! TTS队列输出端模块
2//!
3//! 该模块定义了TTS队列的输出端,实现了rodio的Source trait,可以直接用于音频播放。
4//! 输出端负责按顺序播放队列中的音频片段,并在没有音频片段时播放静音。
5
6use super::{Signal, SoundOrSilence, THRESHOLD, TTSQueueInput};
7use anyhow::{Result, anyhow};
8use rodio::{Sample, Source, source::Zero};
9use std::sync::Arc;
10
11/// TTS队列输出端
12///
13/// 实现了Iterator和Source trait,可以作为音频源直接使用。
14/// 自动处理队列中音频片段的顺序播放。
15///
16/// # 泛型参数
17/// * `T` - 音频源类型,必须实现Source、Send和Clone trait
18pub struct TTSQueueOutput<T>
19where
20    T: Source + Send + Clone,
21{
22    /// 当前正在播放的音频或静音片段
23    pub current: SoundOrSilence<T>,
24
25    /// 当前音频片段关联的信号发送器
26    /// 用于通知播放状态(开始/结束)
27    pub current_signal: Signal,
28
29    /// 当前播放片段在队列中的索引
30    pub index: usize,
31
32    /// 队列输入端的引用
33    pub input: Arc<TTSQueueInput<T>>,
34
35    /// 是否为初始状态
36    /// 初始状态下会播放索引为index的片段,而非index+1的片段
37    pub is_initial: bool,
38}
39
40impl<T> TTSQueueOutput<T>
41where
42    T: Source + Send + Clone,
43{
44    /// 创建一个新的队列输出端
45    ///
46    /// # 参数
47    /// * `input` - 队列输入端的引用
48    /// * `index` - 初始播放索引
49    ///
50    /// # 返回值
51    /// * `TTSQueueOutput<T>` - 新创建的队列输出端
52    pub fn new(input: Arc<TTSQueueInput<T>>, index: usize) -> Self {
53        // 尝试获取指定索引的音频片段
54        let (sound, signal, is_initial) =
55            if let Some((sound, signal)) = input.sounds.lock().unwrap().get(index) {
56                // 如果存在该索引的音频片段,则使用该片段
57                (SoundOrSilence::Sound(sound.clone()), signal.clone(), false)
58            } else {
59                // 如果不存在该索引的音频片段,则创建一个静音片段
60                let silence = Zero::new_samples(1, 44100, THRESHOLD);
61                (SoundOrSilence::Silence(silence), None, true)
62            };
63
64        Self {
65            current: sound,
66            current_signal: signal,
67            index,
68            input,
69            is_initial,
70        }
71    }
72
73    /// 切换到下一个音频片段
74    ///
75    /// 如果队列中还有下一个音频片段,则切换到该片段;
76    /// 如果队列已完成且没有更多片段,则返回错误;
77    /// 如果队列未完成但没有更多片段,则播放静音片段。
78    ///
79    /// # 返回值
80    /// * `Result<()>` - 成功返回Ok,如果队列已完成且没有更多片段则返回Err
81    pub fn go_next(&mut self) -> Result<()> {
82        // 如果当前片段有关联的信号发送器,则发送结束信号
83        if let Some(sender) = self.current_signal.take() {
84            // 发送上一个结束信号
85            sender.try_send(true).ok();
86        }
87
88        // 计算下一个要播放的片段索引
89        let next_index = if self.is_initial {
90            // 如果是初始状态,则播放当前索引的片段
91            self.index
92        } else {
93            // 否则播放下一个索引的片段
94            self.index + 1
95        };
96
97        // 尝试获取下一个音频片段
98        if let Some((sound, signal)) = self.input.sounds.lock().unwrap().get(next_index) {
99            // 如果存在下一个音频片段,则切换到该片段
100            self.current = SoundOrSilence::Sound(sound.clone());
101            self.current_signal = signal.clone();
102            self.index = next_index;
103            self.is_initial = false;
104        } else {
105            // 如果不存在下一个音频片段
106            if self.input.is_finished() {
107                // 如果队列已完成,则返回错误
108                return Err(anyhow!("No more sounds in the queue"));
109            }
110            // 如果队列未完成,则播放静音片段
111            let silence = Zero::new_samples(1, 44100, THRESHOLD);
112            self.current = SoundOrSilence::Silence(silence);
113        }
114
115        // 如果新片段有关联的信号发送器,则发送开始信号
116        if let Some(sender) = &self.current_signal {
117            // 发送新的开始信号
118            sender.try_send(false).ok();
119        }
120
121        Ok(())
122    }
123}
124
125impl<T> Iterator for TTSQueueOutput<T>
126where
127    T: Source + Send + Clone,
128{
129    type Item = Sample;
130
131    fn next(&mut self) -> Option<Self::Item> {
132        // 循环处理音频片段
133        loop {
134            // 尝试从当前音频片段获取下一个采样
135            if let Some(sample) = self.current.next() {
136                return Some(sample);
137            }
138
139            // 如果当前音频片段已播放完毕,则切换到下一个片段
140            if self.go_next().is_err() {
141                // 如果切换失败(队列已完成且没有更多片段),则返回None
142                return None;
143            }
144        }
145    }
146
147    fn size_hint(&self) -> (usize, Option<usize>) {
148        // 返回当前音频片段的大小提示
149        (self.current.size_hint().0, None)
150    }
151}
152
153impl<T> Source for TTSQueueOutput<T>
154where
155    T: Source + Send + Clone,
156{
157    /// 返回当前片段的跨度长度
158    ///
159    /// 该函数确定当前音频片段的跨度长度,用于音频处理中的缓冲区管理。
160    /// 队列中两个音频片段之间的边界也应作为跨度边界处理。
161    ///
162    /// 确定跨度长度的优先级:
163    /// 1. 当前音频片段提供的跨度长度(非零值)
164    /// 2. 当队列为空且未完成时,使用静音片段长度
165    /// 3. 使用音频片段的大小提示(正值)
166    /// 4. 默认使用THRESHOLD常量
167    ///
168    /// # 返回值
169    /// * `Option<usize>` - 当前片段的跨度长度
170    fn current_span_len(&self) -> Option<usize> {
171        // 尝试获取当前片段的跨度长度
172        if let Some(val) = self.current.current_span_len() {
173            if val != 0 {
174                return Some(val);
175            } else if !self.input.is_finished() && self.input.sounds.lock().unwrap().is_empty() {
176                // 下一个片段将是填充的静音片段,其长度为THRESHOLD
177                return Some(THRESHOLD);
178            }
179        }
180
181        // 尝试获取大小提示
182        let (lower_bound, _) = self.current.size_hint();
183        // 迭代器的默认实现返回0,这是一个有问题的值,所以跳过它
184        if lower_bound > 0 {
185            return Some(lower_bound);
186        }
187
188        // 否则使用常量值
189        Some(THRESHOLD)
190    }
191
192    /// 返回音频的声道数
193    fn channels(&self) -> rodio::ChannelCount {
194        self.current.channels()
195    }
196
197    /// 返回音频的采样率
198    fn sample_rate(&self) -> rodio::SampleRate {
199        self.current.sample_rate()
200    }
201
202    /// 返回音频的总时长(无)
203    fn total_duration(&self) -> Option<std::time::Duration> {
204        None
205    }
206}