Skip to content

Commit de7dc57

Browse files
committed
AudioSourceFTP
1 parent ddc5029 commit de7dc57

File tree

3 files changed

+235
-0
lines changed

3 files changed

+235
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @file player-ftp-audiokit.ino
3+
* @brief AudioPlayer example that is using FTP as audio source
4+
* @author Phil Schatzmann
5+
* @copyright GPLv3
6+
*/
7+
8+
#include "WiFi.h"
9+
#include "AudioTools.h"
10+
#include "AudioTools/AudioCodecs/CodecMP3Helix.h"
11+
#include "AudioTools/AudioLibs/AudioBoardStream.h"
12+
#include "AudioTools/Disk/AudioSourceFTP.h"
13+
14+
const char* path = "Music/Tracy Chapman";
15+
const char* ext = "mp3";
16+
const char* ftp_user = "user";
17+
const char* ftp_pwd = "password";
18+
const char* ssid = "ssid";
19+
const char* ssid_pwd = "ssid-password";
20+
WiFiClient cmd;
21+
WiFiClient data;
22+
FTPClient ftp(cmd, data);
23+
AudioSourceFTP source(ftp, path, ext);
24+
AudioBoardStream i2s(AudioKitEs8388V1);
25+
MP3DecoderHelix decoder;
26+
AudioPlayer player(source, i2s, decoder);
27+
28+
void next(bool, int, void*) { player.next(); }
29+
30+
void previous(bool, int, void*) { player.previous(); }
31+
32+
void startStop(bool, int, void*) { player.setActive(!player.isActive()); }
33+
34+
void setup() {
35+
Serial.begin(115200);
36+
AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
37+
FTPLogger::setOutput(Serial);
38+
FTPLogger::setLogLevel(LOG_DEBUG);
39+
40+
// connect to WIFI
41+
WiFi.begin(ssid, ssid_pwd);
42+
while (WiFi.status() != WL_CONNECTED) {
43+
delay(500);
44+
Serial.print(".");
45+
}
46+
Serial.println();
47+
WiFi.setSleep(false);
48+
49+
// connect to ftp
50+
if (!ftp.begin(IPAddress(192,168,1,39), ftp_user, ftp_pwd)){
51+
Serial.println("ftp failed");
52+
stop();
53+
}
54+
55+
// setup output
56+
auto cfg = i2s.defaultConfig(TX_MODE);
57+
if (!i2s.begin(cfg)){
58+
Serial.println("i2s failed");
59+
stop();
60+
}
61+
62+
// setup additional buttons
63+
i2s.addDefaultActions();
64+
i2s.addAction(i2s.getKey(1), startStop);
65+
i2s.addAction(i2s.getKey(4), next);
66+
i2s.addAction(i2s.getKey(3), previous);
67+
68+
// setup player
69+
player.setVolume(0.7);
70+
// load the directory
71+
if (!player.begin()){
72+
Serial.println("player failed");
73+
stop();
74+
}
75+
}
76+
77+
void loop() {
78+
player.copy();
79+
i2s.processActions();
80+
}

src/AudioTools/CoreAudio/AudioPlayer.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,17 @@ class AudioPlayer : public AudioInfoSupport, public VolumeSupport {
389389
return copy(copier.bufferSize());
390390
}
391391

392+
/// Copies all the data
393+
size_t copyAll() {
394+
size_t result = 0;
395+
size_t step = copy();
396+
while(step > 0){
397+
result += step;
398+
step = copy();
399+
}
400+
return result;
401+
}
402+
392403
/// Copies the indicated number of bytes from the source to the decoder: Call this method in the loop.
393404
size_t copy(size_t bytes) {
394405
size_t result = 0;

src/AudioTools/Disk/AudioSourceFTP.h

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#include "ArduinoFTPClient.h"
2+
#include "AudioSource.h"
3+
#include "vector"
4+
5+
namespace audio_tools {
6+
7+
/**
8+
* @brief An AudioSource that uses the
9+
* https://github.com/pschatzmann/TinyFTPClient library to retrieve files from a
10+
* FTP Server. You need to provide an FTPClient object to the constructor and
11+
* make sure that you open it before you access the files. The storage of the
12+
* expanded file names is done on the heap, so in order to limit the requested
13+
* memory we can limit the number of files.
14+
*
15+
* @ingroup player
16+
* @author Phil Schatzmann
17+
* @copyright GPLv3
18+
*/
19+
20+
21+
class AudioSourceFTP : public AudioSource {
22+
public:
23+
AudioSourceFTP(FTPClient& client, const char* path, const char* ext,
24+
int files = 0) {
25+
p_client = &client;
26+
timeout_auto_next_value = 5000;
27+
if (path) p_path = path;
28+
setMaxFiles(files);
29+
}
30+
31+
/// Resets the actual data
32+
void begin()override {
33+
TRACED();
34+
idx = 0;
35+
files.clear();
36+
addDirectory(p_path);
37+
}
38+
39+
/// Resets the actual data
40+
void end() {
41+
idx = 0;
42+
files.clear();
43+
}
44+
45+
/// Returns next audio stream
46+
Stream* nextStream(int offset) override {
47+
int tmp_idx = idx + offset;
48+
if (!isValidIdx(tmp_idx)) return nullptr;
49+
return selectStream(tmp_idx);
50+
};
51+
52+
/// Returns previous audio stream
53+
Stream* previousStream(int offset) override { return nextStream(-offset); };
54+
55+
/// Returns audio stream at the indicated index (the index is zero based, so
56+
/// the first value is 0!)
57+
Stream* selectStream(int index) override {
58+
if (!isValidIdx(index)) return nullptr;
59+
idx = index;
60+
if (file) {
61+
file.close(); // close the previous file
62+
}
63+
file = p_client->open(files[idx].name());
64+
return &file;
65+
}
66+
67+
/// Retrieves all files and returns the actual index of the stream
68+
int index() override { return idx; }
69+
70+
/// Returns the FTPFile for the indicated path
71+
Stream* selectStream(const char* path) override {
72+
TRACED();
73+
files.clear();
74+
idx = 0;
75+
addDirectory(path);
76+
return selectStream(0);
77+
}
78+
79+
/// provides the actual stream (e.g. file) name or url
80+
const char* toStr() override {
81+
return file.name();
82+
}
83+
84+
/// Defines the max number of files (if value is >0)
85+
void setMaxFiles(int maxCount) { max_files = maxCount; }
86+
87+
/// Adds all the files of a directory
88+
bool addDirectory(const char* path) {
89+
TRACED();
90+
if (p_client == nullptr) return false;
91+
FTPFile dir = p_client->open(path);
92+
addFiles(dir, 1);
93+
return true;
94+
}
95+
96+
/// Returns the number of available files
97+
size_t size() { return files.size(); }
98+
99+
protected:
100+
std::vector<FTPFile> files;
101+
FTPClient* p_client = nullptr;
102+
FTPFile file;
103+
int idx = 0;
104+
size_t max_files = 0;
105+
const char* p_ext = nullptr;
106+
const char* p_path = "/";
107+
bool is_first = true;
108+
109+
/// Adds all files recursively
110+
void addFiles(FTPFile& dir, int level) {
111+
if (!endsWith(dir.name(), p_ext)) {
112+
LOGI("adding file %s", dir.name());
113+
files.push_back(std::move(dir));
114+
} else {
115+
for (const auto& file : p_client->ls(dir.name())) {
116+
if (endsWith(file.name(), p_ext)) {
117+
LOGI("adding file %s", file.name());
118+
files.push_back(std::move(file));
119+
}
120+
if (max_files > 0 && files.size() >= max_files) {
121+
LOGI("max files reached: %d", max_files);
122+
return; // stop if we reached the max number of files
123+
}
124+
}
125+
}
126+
}
127+
128+
bool isValidIdx(int index) {
129+
if (index < 0) return false;
130+
if (index >= files.size()) {
131+
LOGE("index %d is out of range (size: %d)", index, files.size());
132+
return false;
133+
}
134+
return true;
135+
}
136+
137+
bool endsWith(const char* file, const char* ext) {
138+
if (file == nullptr) return false;
139+
if (p_ext == nullptr) return true;
140+
return (StrView(file).endsWith(ext));
141+
}
142+
};
143+
144+
} // namespace audio_tools

0 commit comments

Comments
 (0)