You can now also play MP3s streamed from the internet. (Very rough & wonky code. More or less proof-of-concept right now.)
This commit is contained in:
		| @@ -3,19 +3,21 @@ | |||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
| #include <SD.h> | #include <SD.h> | ||||||
| #include "config.h" | #include "config.h" | ||||||
|  | #include <HTTPClient.h> | ||||||
|  |  | ||||||
| class DataSource { | class DataSource { | ||||||
| private: | private: | ||||||
| public: | public: | ||||||
| 	DataSource() {}; | 	DataSource() {}; | ||||||
| 	~DataSource() {}; | 	virtual ~DataSource() {}; | ||||||
| 	virtual size_t read(uint8_t* buf, size_t len) = 0; | 	virtual size_t read(uint8_t* buf, size_t len) = 0; | ||||||
| 	virtual uint8_t read() = 0; | 	virtual uint8_t read() = 0; | ||||||
| 	virtual size_t position() = 0; | 	virtual size_t position() = 0; | ||||||
| 	virtual void seek(size_t position) = 0; | 	virtual void seek(size_t position) = 0; | ||||||
| 	virtual size_t size() = 0; | 	virtual size_t size() = 0; | ||||||
| 	virtual void close() = 0; | 	virtual void close() = 0; | ||||||
| 	virtual void skip_id3_tag(); | 	virtual void skip_id3_tag() {}; | ||||||
|  | 	virtual bool usable() = 0; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class SDDataSource : public DataSource { | class SDDataSource : public DataSource { | ||||||
| @@ -31,4 +33,23 @@ public: | |||||||
| 	size_t size(); | 	size_t size(); | ||||||
| 	void close(); | 	void close(); | ||||||
| 	void skip_id3_tag(); | 	void skip_id3_tag(); | ||||||
|  | 	bool usable(); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class HTTPSDataSource : public DataSource { | ||||||
|  | private: | ||||||
|  | 	WiFiClient* _stream = NULL; | ||||||
|  | 	HTTPClient* _http = NULL; | ||||||
|  | 	uint32_t _length; | ||||||
|  | 	uint32_t _position; | ||||||
|  | public: | ||||||
|  | 	HTTPSDataSource(String url, uint32_t offset=0); | ||||||
|  | 	~HTTPSDataSource(); | ||||||
|  | 	size_t read(uint8_t* buf, size_t len); | ||||||
|  | 	uint8_t read(); | ||||||
|  | 	size_t position(); | ||||||
|  | 	void seek(size_t position); | ||||||
|  | 	size_t size(); | ||||||
|  | 	void close(); | ||||||
|  | 	bool usable(); | ||||||
| }; | }; | ||||||
| @@ -11,7 +11,7 @@ private: | |||||||
| 	bool _shuffled = false; | 	bool _shuffled = false; | ||||||
| 	std::vector<String> _files; | 	std::vector<String> _files; | ||||||
| public: | public: | ||||||
| 	Playlist(String path); | 	Playlist(String path, bool is_url=false); | ||||||
| 	void start(); | 	void start(); | ||||||
| 	bool has_track_next(); | 	bool has_track_next(); | ||||||
| 	bool has_track_prev(); | 	bool has_track_prev(); | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ size_t SDDataSource::position() { return _file.position(); } | |||||||
| void SDDataSource::seek(size_t position) { _file.seek(position); } | void SDDataSource::seek(size_t position) { _file.seek(position); } | ||||||
| size_t SDDataSource::size() { return _file.size(); } | size_t SDDataSource::size() { return _file.size(); } | ||||||
| void SDDataSource::close() { _file.close(); } | void SDDataSource::close() { _file.close(); } | ||||||
|  | bool SDDataSource::usable() { return _file; } | ||||||
|  |  | ||||||
| void SDDataSource::skip_id3_tag() { | void SDDataSource::skip_id3_tag() { | ||||||
| 	uint32_t original_position = _file.position(); | 	uint32_t original_position = _file.position(); | ||||||
| @@ -33,3 +34,61 @@ void SDDataSource::skip_id3_tag() { | |||||||
| 		_file.seek(original_position); | 		_file.seek(original_position); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ////////////// HTTPSDataSource ////////////// | ||||||
|  | HTTPSDataSource::HTTPSDataSource(String url, uint32_t offset) { | ||||||
|  | 	uint8_t tries_left = 5; | ||||||
|  | 	int status; | ||||||
|  | 	do { | ||||||
|  | 		if (tries_left == 0) { | ||||||
|  | 			ERROR("Redirection loop? Cancelling!\n"); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		tries_left--; | ||||||
|  | 		DEBUG("Connecting to %s...\n", url.c_str()); | ||||||
|  | 		if (_http) delete _http; | ||||||
|  | 		_http = new HTTPClient(); | ||||||
|  | 		const char* headers[] = {"location"}; | ||||||
|  | 		_http->collectHeaders(headers, 1); | ||||||
|  | 		bool result = _http->begin(url); | ||||||
|  | 		DEBUG("HTTP->begin result: %d\n", result); | ||||||
|  | 		if (!result) return; | ||||||
|  | 		status = _http->GET(); | ||||||
|  | 		DEBUG("Status code: %d\n", status); | ||||||
|  | 		if (status == HTTP_CODE_FOUND || status==HTTP_CODE_MOVED_PERMANENTLY || status==HTTP_CODE_TEMPORARY_REDIRECT) { | ||||||
|  | 			if (_http->hasHeader("Location")) { | ||||||
|  | 				url = _http->header("Location"); | ||||||
|  | 			} else if (_http->hasHeader("location")) { | ||||||
|  | 				url = _http->header("location"); | ||||||
|  | 			} else { | ||||||
|  | 				ERROR("Got redirection HTTP code, but could not find Location header.\n"); | ||||||
|  | 				for(int i=0; i<_http->headers(); i++) { | ||||||
|  | 					DEBUG("  Header: %s=%s\n", _http->headerName(i).c_str(), _http->header(i).c_str()); | ||||||
|  | 				} | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 		} else if (status != HTTP_CODE_OK) { | ||||||
|  | 			DEBUG("Unexpected HTTP return code. Cancelling.\n"); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} while (status != HTTP_CODE_OK); | ||||||
|  | 	_length = _http->getSize(); | ||||||
|  | 	DEBUG("Content-Length: %d\n", _length); | ||||||
|  | 	_stream = _http->getStreamPtr(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | HTTPSDataSource::~HTTPSDataSource() { | ||||||
|  | 	if (_stream) _stream->stop(); | ||||||
|  | 	_http->end(); | ||||||
|  | 	delete _stream; | ||||||
|  | 	delete _http; | ||||||
|  | } | ||||||
|  | bool HTTPSDataSource::usable() { return _http && _stream; } | ||||||
|  | size_t HTTPSDataSource::read(uint8_t* buf, size_t len) { size_t result = _stream->read(buf, len); _position += result; return result; } | ||||||
|  | uint8_t HTTPSDataSource::read() { _position++; return _stream->read(); } | ||||||
|  | size_t HTTPSDataSource::position() { return _position; } | ||||||
|  | void HTTPSDataSource::seek(size_t position) { return; /* TODO */ } | ||||||
|  | size_t HTTPSDataSource::size() { return _length; } | ||||||
|  | void HTTPSDataSource::close() { _stream->stop(); } | ||||||
| @@ -545,10 +545,16 @@ bool Player::play() { | |||||||
| void Player::_play_file(String file, uint32_t file_offset) { | void Player::_play_file(String file, uint32_t file_offset) { | ||||||
| 	INFO("play_file('%s', %d)\n", file.c_str(), file_offset); | 	INFO("play_file('%s', %d)\n", file.c_str(), file_offset); | ||||||
| 	_spi->select_sd(); | 	_spi->select_sd(); | ||||||
| 	_file = new SDDataSource(file); | 	if (file.startsWith("/")) { | ||||||
|  | 		_file = new SDDataSource(file); | ||||||
|  | 	} else if (file.startsWith("https://")) { | ||||||
|  | 		_file = new HTTPSDataSource(file); | ||||||
|  | 	} else { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
| 	_file_size = _file->size(); | 	_file_size = _file->size(); | ||||||
| 	_spi->select_sd(false); | 	_spi->select_sd(false); | ||||||
| 	if (!_file) { | 	if (!_file || !_file->usable()) { | ||||||
| 		DEBUG("Could not open file %s", file.c_str()); | 		DEBUG("Could not open file %s", file.c_str()); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -5,7 +5,11 @@ | |||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <ArduinoJson.h> | #include <ArduinoJson.h> | ||||||
|  |  | ||||||
| Playlist::Playlist(String path) { | Playlist::Playlist(String path, bool is_url) { | ||||||
|  | 	if (is_url) { | ||||||
|  | 		_files.push_back(path); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
| 	// Add files to _files | 	// Add files to _files | ||||||
| 	SPIMaster::select_sd(); | 	SPIMaster::select_sd(); | ||||||
| 	TRACE("Examining folder %s...\n", path.c_str()); | 	TRACE("Examining folder %s...\n", path.c_str()); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user