Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "PlaybackManager.h"
9
10PlaybackManager::PlaybackManager(NonnullRefPtr<Audio::ConnectionToServer> connection)
11 : m_connection(connection)
12{
13 // FIXME: The buffer enqueuing should happen on a wholly independent second thread.
14 m_timer = Core::Timer::create_repeating(PlaybackManager::update_rate_ms, [&]() {
15 if (!m_loader)
16 return;
17 next_buffer();
18 }).release_value_but_fixme_should_propagate_errors();
19 m_device_sample_rate = connection->get_sample_rate();
20}
21
22void PlaybackManager::set_loader(NonnullRefPtr<Audio::Loader>&& loader)
23{
24 stop();
25 m_loader = loader;
26 if (m_loader) {
27 m_total_length = m_loader->total_samples() / static_cast<float>(m_loader->sample_rate());
28 m_device_samples_per_buffer = PlaybackManager::buffer_size_ms / 1000.0f * m_device_sample_rate;
29 m_samples_to_load_per_buffer = PlaybackManager::buffer_size_ms / 1000.0f * m_loader->sample_rate();
30 m_resampler = Audio::ResampleHelper<Audio::Sample>(m_loader->sample_rate(), m_device_sample_rate);
31 m_timer->start();
32 } else {
33 m_timer->stop();
34 }
35}
36
37void PlaybackManager::stop()
38{
39 set_paused(true);
40 m_connection->async_clear_buffer();
41
42 if (m_loader)
43 (void)m_loader->reset();
44}
45
46void PlaybackManager::play()
47{
48 set_paused(false);
49}
50
51void PlaybackManager::loop(bool loop)
52{
53 m_loop = loop;
54}
55
56void PlaybackManager::seek(int const position)
57{
58 if (!m_loader)
59 return;
60
61 bool paused_state = m_paused;
62 set_paused(true);
63
64 [[maybe_unused]] auto result = m_loader->seek(position);
65
66 m_connection->clear_client_buffer();
67 m_connection->async_clear_buffer();
68 if (!paused_state)
69 set_paused(false);
70}
71
72void PlaybackManager::pause()
73{
74 set_paused(true);
75}
76
77void PlaybackManager::set_paused(bool paused)
78{
79 m_paused = paused;
80 if (m_paused)
81 m_connection->async_pause_playback();
82 else
83 m_connection->async_start_playback();
84}
85
86bool PlaybackManager::toggle_pause()
87{
88 if (m_paused) {
89 play();
90 } else {
91 pause();
92 }
93 return m_paused;
94}
95
96void PlaybackManager::next_buffer()
97{
98 if (on_update)
99 on_update();
100
101 if (m_paused)
102 return;
103
104 while (m_connection->remaining_samples() < m_device_samples_per_buffer * always_enqueued_buffer_count) {
105 bool all_samples_loaded = (m_loader->loaded_samples() >= m_loader->total_samples());
106 bool audio_server_done = (m_connection->remaining_samples() == 0);
107
108 if (all_samples_loaded && audio_server_done) {
109 stop();
110 if (on_finished_playing)
111 on_finished_playing();
112 return;
113 }
114
115 auto buffer_or_error = m_loader->get_more_samples(m_samples_to_load_per_buffer);
116 if (buffer_or_error.is_error()) {
117 // FIXME: These errors should be shown to the user instead of being logged and then ignored
118 dbgln("Error while loading samples: {}", buffer_or_error.error().description);
119 return;
120 }
121 auto buffer = buffer_or_error.release_value();
122 m_current_buffer.swap(buffer);
123 VERIFY(m_resampler.has_value());
124
125 m_resampler->reset();
126 // FIXME: Handle OOM better.
127 auto resampled = MUST(FixedArray<Audio::Sample>::create(m_resampler->resample(move(m_current_buffer)).span()));
128 m_current_buffer.swap(resampled);
129 MUST(m_connection->async_enqueue(m_current_buffer));
130 }
131}